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, ))
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)
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} )
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"])
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)
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, ))
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
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"'
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, ))
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, ))
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, ))
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, ))
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, ))
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, ))
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, ) )
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, ))
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, ))
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, ) )
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, ) )
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, ) )
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, ) )
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, ))
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, ))
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, ))
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, ))
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"),
# 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", [],