Ejemplo n.º 1
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        fh = io.BytesIO()
        content = io.BytesIO(self.file_content.encode("utf-8"))
        with tarfile.open(fileobj=fh, mode="w:gz") as tar:
            info = tarfile.TarInfo(self.file_name)
            info.size = len(self.file_content)
            tar.addfile(info, content)

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/tar/add",
            files={self.file_name: fh},
        )

        context.report.add_issue(
            Issue(
                title="Anyone can upload compressed data to the node",
                description=
                ("Anyone is able to upload files to the node. An attacker can use this to "
                 "upload large amounts of data and thus prevent the node from accepting "
                 "further uploads, performing a Denial of Service (DoS) attack."
                 ),
                raw_data=payload,
                severity=Severity.HIGH,
            ))
Ejemplo n.º 2
0
def test_report_add_incomplete_issue():
    report = Report("127.0.0.1:8545")
    assert report.issues == []

    issue = Issue(title="test", description="test")  # missing severity!
    with pytest.raises(ValueError):
        report.add_issue(issue)
Ejemplo n.º 3
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target, route="/api/v0/pin/rm", params={"arg": self.pin}
        )

        context.report.add_issue(
            Issue(
                title="Anyone can remove the node's pins",
                description=(
                    "It is possible to remove all the content IDs that "
                    "are pinned to the node's local storage. This poses "
                    "a risk to data availability as an attacker can unpin "
                    "any file."
                ),
                raw_data=payload,
                severity=Severity.HIGH,
            )
        )

        if self.restore:
            # Attempt to restore the deleted pin
            # TODO: log message if restoration succeeded/failed
            self.get_rpc_json(
                target=context.target, route="/api/v0/pin/add", params={"arg": self.pin}
            )
Ejemplo n.º 4
0
def test_report_add_issue():
    report = Report("127.0.0.1:8545")
    assert report.issues == []

    issue = Issue(title="test", description="test", severity=Severity.NONE)
    report.add_issue(issue)
    assert report.issues == [issue]
    assert len(report.to_dict()["issues"])
Ejemplo n.º 5
0
def test_issue_repr():
    i = Issue(
        uuid=TEST_UUID,
        title="test",
        description="test",
        severity=Severity.NONE,
        raw_data=None,
    )
    assert str(i.severity) in str(i)
    assert str(i.title) in str(i)
Ejemplo n.º 6
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        key_list = self.get_rpc_json(target=context.target,
                                     route="/api/v0/key/list")

        context.report.add_issue(
            Issue(
                title="Key List Information Leak",
                description=
                ("Anyone is able to list the keys registered on the node. The name of "
                 "a key can leak information as well and is required for other actions "
                 "such as exporting the key contents."),
                severity=Severity.MEDIUM,
                raw_data=key_list,
            ))

        if not self.export:
            return

        for key in key_list.get("Keys", []):
            try:
                payload = self.get_rpc_json(
                    target=context.target,
                    route="/api/v0/key/export",
                    params={"arg": key["Name"]},
                    raw=True,
                )
            except PluginException:
                continue

            context.report.add_issue(
                Issue(
                    title="Unauthorized Key Export",
                    description=
                    ("Anyone can export keys from the node. All secrets should be invalidated, "
                     "rotated, and reapplied. The endpoint must be protected against future "
                     "unauthorized use."),
                    severity=Severity.CRITICAL,
                    raw_data=payload,
                ))
Ejemplo n.º 7
0
def test_valid_issue():
    title = "test"
    description = "test"
    severity = Severity.NONE
    raw_data = "test"
    issue = Issue(
        title=title, description=description, severity=severity, raw_data=raw_data
    )

    assert issue.title == title
    assert issue.description == description
    assert issue.severity == severity
    assert issue.raw_data == raw_data
Ejemplo n.º 8
0
def test_issue_dict():
    title = "title"
    description = "description"
    severity = Severity.NONE
    raw_data = "raw_data"
    issue = Issue(
        title=title, description=description, severity=severity, raw_data=raw_data
    )
    issue_dict = issue.to_dict()

    assert issue_dict["title"] == title
    assert issue_dict["description"] == description
    assert issue_dict["severity"] == severity.name.lower()
    assert issue_dict["raw"] == '"raw_data"'
Ejemplo n.º 9
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(target=context.target,
                                    route="/api/v0/version/deps",
                                    raw=True).split("\n")
        payload = [json.loads(s) for s in payload if s != ""]

        context.report.add_issue(
            Issue(
                title="Dependency Version Information Leak",
                description=
                ("Dependency version information is exposed. "
                 "This allows an attacker to obtain information about the system's Go version, "
                 "operating system, as well as the IPFS node's version and origin repository"
                 ),
                severity=Severity.LOW,
                raw_data=payload,
            ))

        if not self.check_dependencies:
            return

        for dependency in payload:
            if dependency.get("ReplacedBy", "") != "":
                context.report.add_issue(
                    Issue(
                        title="Outdated Dependency",
                        description=
                        ("The IPFS node has been compiled with an old dependency version. "
                         "Consider upgrading it for the latest feature and security updates."
                         ),
                        severity=Severity.LOW,
                        raw_data=dependency,
                    ))
Ejemplo n.º 10
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(target=context.target,
                                    route="/api/v0/shutdown",
                                    raw=True)
        context.report.add_issue(
            Issue(
                title="Exposed Shutdown Endpoint",
                description=
                ("Anyone can shut down the IPFS daemon. This plugin has shut down the node. "
                 "This is the highest possible threat to availability."),
                severity=Severity.CRITICAL,
                raw_data=payload,
            ))
Ejemplo n.º 11
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(target=context.target,
                                    route="/api/v0/log/ls")
        context.report.add_issue(
            Issue(
                title="Exposed Logging Subsystem Data",
                description=
                ("It is possible to list the logging subsystems that the node "
                 "is using. This may be used by an attacker to find non-standard "
                 "customizations on the node, as well as fingerprint the node setup "
                 "for identification."),
                severity=Severity.LOW,
                raw_data=payload,
            ))
Ejemplo n.º 12
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/p2p/stream/ls",
        )
        context.report.add_issue(
            Issue(
                title="Exposed P2P Stream List",
                description=
                ("Anyone is able to list the active P2P streams on this node. "
                 "This method may leak internal information on other peer-to-peer services "
                 "and connections on this node."),
                severity=Severity.LOW,
                raw_data=payload,
            ))
Ejemplo n.º 13
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload, status = self.fetch_ui(context.target, self.route)

        if status == 200:
            context.report.add_issue(
                Issue(
                    title="Exposed Web UI",
                    description=
                    ("Anyone can access the Web UI. A plethora of administrative "
                     "actions can be done through the web interface. This includes "
                     "changing the node's configuration, which can be used to open "
                     "other potential attack vectors."),
                    severity=Severity.HIGH,
                    raw_data=payload,
                ))
Ejemplo n.º 14
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(target=context.target,
                                    route="/api/v0/log/tail",
                                    stream_limit=2,
                                    timeout=5)

        context.report.add_issue(
            Issue(
                title="Exposed System Log Data",
                description=
                ("Anyone can list log messages generated by the node. Log messages, "
                 "especially debug-level ones, can leak sensitive information about "
                 "the node's setup and operations running on it."),
                severity=Severity.MEDIUM,
                raw_data=payload,
            ))
Ejemplo n.º 15
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target, route="/api/v0/pin/add", params={"arg": self.cid}
        )

        context.report.add_issue(
            Issue(
                title="Anyone can pin data to the node",
                description=(
                    "Open pinning can enable an attacker to flush a large amount of"
                    "random data onto the node's disk until storage space is exhausted,"
                    "thus performing a denial of service attack against future uploads/pins."
                ),
                raw_data=payload,
                severity=Severity.HIGH,
            )
        )
Ejemplo n.º 16
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/p2p/stream/close",
            params={"all": True},
            raw=True,
        )
        context.report.add_issue(
            Issue(
                title="Exposed P2P Stream Management endpoint",
                description=(
                    "Anyone is able to close active P2P streams on this node. "
                    "This exposed functionality may be used by an attacker to "
                    "disrupt the node's availability and block connections."),
                severity=Severity.HIGH,
                raw_data=payload,
            ))
Ejemplo n.º 17
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/version",
        )

        context.report.add_issue(
            Issue(
                title="Version Information Leak",
                description=
                ("Version information of the node and its execution environment is exposed. "
                 "This allows an attacker to obtain information about the system's Go version, "
                 "operating system, as well as the IPFS node's version and origin repository"
                 ),
                severity=Severity.LOW,
                raw_data=payload,
            ))
Ejemplo n.º 18
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/pin/ls",
        )

        context.report.add_issue(
            Issue(
                title="Anyone can list the node's pins",
                description=(
                    "It is possible to list all the content IDs that "
                    "are pinned to the node's local storage."
                ),
                raw_data=payload,
                severity=Severity.LOW,
            )
        )
Ejemplo n.º 19
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target, route="/api/v0/files/ls", params={"arg": self.path}
        )

        context.report.add_issue(
            Issue(
                title="Found an Exposed UNIX Filesystem Root",
                description=(
                    "The UNIX root directory path is leaking contents of UNIX filesystem "
                    "objects. An attacker can use this endpoint along with the /files/read "
                    "endpoint to enumerate potentially confidential data on the system."
                ),
                severity=Severity.MEDIUM,
                raw_data=payload,
            )
        )
Ejemplo n.º 20
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/filestore/ls",
        )

        context.report.add_issue(
            Issue(
                title="Found Exposed Filestore Objects",
                description=(
                    "The filestore endpoint is leaking contents of its objects. An attacker "
                    "can use this endpoint to enumerate potentially confidential data on the "
                    "system."
                ),
                severity=Severity.MEDIUM,
                raw_data=payload,
            )
        )
Ejemplo n.º 21
0
    def check_paths(self, context: Context, endpoint: str):
        for ipfs_path in self.cid_paths:
            try:
                payload = self.get_rpc_json(
                    target=context.target, route=endpoint, params={"arg": ipfs_path}
                )
            except PluginException:
                continue

            context.report.add_issue(
                Issue(
                    title="Found an Exposed IPFS Content ID",
                    description=(
                        "A common IPFS file path is leaking directory contents of UNIX filesystem "
                        "objects. Depending on where IPFS has been mounted, this can leak "
                        f"confidential information. Endpoint: {endpoint}"
                    ),
                    severity=Severity.MEDIUM,
                    raw_data=payload,
                )
            )
Ejemplo n.º 22
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/add",
            files={self.file_name: self.file_content.encode("utf-8")},
        )

        context.report.add_issue(
            Issue(
                title="Anyone can upload data to the node",
                description=
                ("Anyone is able to upload files to the node. An attacker can use this to "
                 "upload large amounts of data and thus prevent the node from accepting "
                 "further uploads, performing a Denial of Service (DoS) attack."
                 ),
                raw_data=payload,
                severity=Severity.HIGH,
            ))
Ejemplo n.º 23
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        # TODO: validate that this doesn't trigger an internal server error
        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/p2p/listen",
            params=[("arg", "/teatime/"), ("arg", "127.0.0.1")],
            raw=True,
        )
        context.report.add_issue(
            Issue(
                title="Exposed P2P Management endpoint",
                description=(
                    "Anyone is able to register P2P listeners on this node. "
                    "This exposed functionality may be used by an attacker to "
                    "disrupt the node's availability and block connections."),
                severity=Severity.HIGH,
                raw_data=payload,
            ))
Ejemplo n.º 24
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/commands",
        )

        for command in ALL_COMMANDS:
            item = _get_by_path(payload, command)
            if item is not None and (command in self.denylist
                                     or command not in self.allowlist):
                context.report.add_issue(
                    Issue(
                        title="Forbidden Method is Exposed",
                        description=
                        ("A forbidden API method is open to the Internet. Attackers "
                         "may be able to use the exposed functionality to cause undesired "
                         "effects to the system."),
                        severity=Severity.HIGH,
                        raw_data=item,
                    ))
Ejemplo n.º 25
0
    def _check(self, context: Context):
        if context.node_type != NodeType.IPFS:
            return

        payload = self.get_rpc_json(
            target=context.target,
            route="/api/v0/log/level",
            params=[("arg", self.subsystem), ("arg", "level")],
        )

        context.report.add_issue(
            Issue(
                title="Exposed System Log Management",
                description=
                ("Anyone can change the log level of messages generated by the node. "
                 "Log messages, especially debug-level ones, can leak sensitive information "
                 "about the node's setup and operations running on it. An attacker may unlock "
                 "additional information by enabling debug logs. This could also results in "
                 "degraded performance, espeically when logs are stored in local files, or "
                 "in log aggregation systems unable to handle the load."),
                severity=Severity.MEDIUM,
                raw_data=payload,
            ))
Ejemplo n.º 26
0
    issue = Issue(title=title,
                  description=description,
                  severity=severity,
                  raw_data=raw_data)

    assert issue.title == title
    assert issue.description == description
    assert issue.severity == severity
    assert issue.raw_data == raw_data


@pytest.mark.parametrize(
    "issue,complete",
    (
        pytest.param(
            Issue(title="test", description="test", severity=Severity.NONE),
            True,
            id="complete no raw data",
        ),
        pytest.param(
            Issue(
                title="test",
                description="test",
                severity=Severity.NONE,
                raw_data="test",
            ),
            True,
            id="complete with raw data",
        ),
        pytest.param(
            Issue(title="test", description="test"),
Ejemplo n.º 27
0
# Shutdown
TESTCASES += [
    pytest.param(
        Shutdown(),
        NodeType.IPFS,
        ({
            "text": ""
        }, ),
        "/api/v0/shutdown",
        [
            Issue(
                uuid=TEST_UUID,
                title="Exposed Shutdown Endpoint",
                description=
                ("Anyone can shut down the IPFS daemon. This plugin has shut down the node. "
                 "This is the highest possible threat to availability."),
                severity=Severity.CRITICAL,
                raw_data="",
            )
        ],
        id="Shutdown success issue logged",
    ),
    pytest.param(
        Shutdown(),
        NodeType.IPFS,
        ({
            "status_code": 403
        }, ),
        "/api/v0/shutdown",
        [],