rename secondary to remote :), complete the previous commit changes

This commit is contained in:
am0o0
2024-05-29 16:47:37 +02:00
parent 52a809145e
commit b9edcb7943
21 changed files with 60 additions and 45 deletions

View File

@@ -2,10 +2,10 @@
<qhelp>
<overview>
<p>
Allowing users to execute arbitrary commands using an SSH connection on a secondary server can lead to security issues unless you implement proper authorization.
Allowing users to execute arbitrary commands using an SSH connection on a remote server can lead to security issues unless you implement proper authorization.
</p>
<p>
Assume that you connect to a secondary system via SSH connection from your main or local server that accepts user-controlled data and has interaction with users that you don't trust, passing these data to SSH API as a part of a command that will be executed on a secondary remote server can lead to security issues. You should consider proper authorization rules very carefully.
Assume that you connect to a remote system via SSH connection from your main or local server that accepts user-controlled data and has interaction with users that you don't trust, passing these data to SSH API as a part of a command that will be executed on a secondary remote server can lead to security issues. You should consider proper authorization rules very carefully.
</p>
</overview>
<recommendation>

View File

@@ -12,10 +12,10 @@
*/
import python
import experimental.semmle.python.security.SecondaryServerCmdInjection
import SecondaryCommandInjectionFlow::PathGraph
import experimental.semmle.python.security.RemoteCommandExecution
import RemoteCommandExecutionFlow::PathGraph
from SecondaryCommandInjectionFlow::PathNode source, SecondaryCommandInjectionFlow::PathNode sink
where SecondaryCommandInjectionFlow::flowPath(source, sink)
from RemoteCommandExecutionFlow::PathNode source, RemoteCommandExecutionFlow::PathNode sink
where RemoteCommandExecutionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
"a user-provided value"

View File

@@ -15,14 +15,29 @@ private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
private import semmle.python.Concepts
/** Provides classes for modeling remote server command execution related APIs. */
/**
* A data-flow node that executes an operating system command,
* on a remote server likely by SSH connections.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `RemoteCommandExecution::Range` instead.
*/
class RemoteCommandExecution extends DataFlow::Node instanceof RemoteCommandExecution::Range {
/** Holds if a shell interprets `arg`. */
predicate isShellInterpreted(DataFlow::Node arg) { super.isShellInterpreted(arg) }
/** Gets the argument that specifies the command to be executed. */
DataFlow::Node getCommand() { result = super.getCommand() }
}
/** Provides classes for modeling new remote server command execution APIs. */
module RemoteCommandExecution {
/**
* A data-flow node that executes an operating system command,
* on a remote server likely by SSH connections.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `SystemCommandExecution` instead.
* extend `RemoteCommandExecution` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the command to be executed. */

View File

@@ -6,11 +6,11 @@ import semmle.python.dataflow.new.internal.DataFlowPublic
import codeql.util.Unit
import experimental.semmle.python.Concepts
module SecondaryCommandInjectionConfig implements DataFlow::ConfigSig {
module RemoteCommandExecutionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof SecondaryCommandInjection }
predicate isSink(DataFlow::Node sink) { sink = any(RemoteCommandExecution rce).getCommand() }
}
/** Global taint-tracking for detecting "secondary server command injection" vulnerabilities. */
module SecondaryCommandInjectionFlow = TaintTracking::Global<SecondaryCommandInjectionConfig>;
module RemoteCommandExecutionFlow = TaintTracking::Global<RemoteCommandExecutionConfig>;

View File

@@ -14,6 +14,6 @@ session.userauth_password("user", "password")
@app.get("/bad1")
async def bad1(cmd: str):
async with asyncssh.connect('localhost') as conn:
result = await conn.run(cmd, check=True) # $ result=BAD getSecondaryCommand=cmd
result = await conn.run(cmd, check=True) # $ result=BAD getRemoteCommand=cmd
print(result.stdout, end='')
return {"success": "Dangerous"}

View File

@@ -6,16 +6,16 @@ private import semmle.python.dataflow.new.internal.PrintNode
import experimental.semmle.python.Concepts
module SystemCommandExecutionTest implements TestSig {
string getARelevantTag() { result = "getSecondaryCommand" }
string getARelevantTag() { result = "getRemoteCommand" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(SecondaryCommandInjection sci, DataFlow::Node command |
command = sci and
exists(RemoteCommandExecution sci, DataFlow::Node command |
command = sci.getCommand() and
location = command.getLocation() and
element = command.toString() and
value = prettyNodeForInlineTest(command) and
tag = "getSecondaryCommand"
tag = "getRemoteCommand"
)
}
}

View File

@@ -0,0 +1,4 @@
import python
import TestUtilities.dataflow.DataflowQueryTest
import experimental.semmle.python.security.RemoteCommandExecution
import FromTaintTrackingConfig<RemoteCommandExecutionConfig>

View File

@@ -17,9 +17,9 @@ cisco_881 = {
@app.get("/bad1")
async def bad1(cmd: str):
net_connect = ConnectHandler(**cisco_881)
net_connect.send_command(command_string=cmd) # $ result=BAD getSecondaryCommand=cmd
net_connect.send_command_expect(command_string=cmd) # $ result=BAD getSecondaryCommand=cmd
net_connect.send_command_timing(command_string=cmd) # $ result=BAD getSecondaryCommand=cmd
net_connect.send_multiline(commands=[[cmd, "expect"]]) # $ result=BAD getSecondaryCommand=List
net_connect.send_multiline_timing(commands=cmd) # $ result=BAD getSecondaryCommand=cmd
net_connect.send_command(command_string=cmd) # $ result=BAD getRemoteCommand=cmd
net_connect.send_command_expect(command_string=cmd) # $ result=BAD getRemoteCommand=cmd
net_connect.send_command_timing(command_string=cmd) # $ result=BAD getRemoteCommand=cmd
net_connect.send_multiline(commands=[[cmd, "expect"]]) # $ result=BAD getRemoteCommand=List
net_connect.send_multiline_timing(commands=cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}

View File

@@ -13,9 +13,9 @@ app = FastAPI()
@app.get("/bad1")
async def bad1(cmd: str):
ssh.send(cmd) # $ result=BAD getSecondaryCommand=cmd
ssh.send(cmd) # $ result=BAD getRemoteCommand=cmd
ssh.prompt()
ssh.sendline(cmd) # $ result=BAD getSecondaryCommand=cmd
ssh.sendline(cmd) # $ result=BAD getRemoteCommand=cmd
ssh.prompt()
ssh.logout()
return {"success": stdout}

View File

@@ -0,0 +1 @@
experimental/Security/CWE-074/remoteCommandExecution/RemoteCommandExecution.ql

View File

@@ -21,19 +21,19 @@ async def bad1(cmd: str):
}
driver = AsyncIOSXEDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncIOSXRDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncNXOSDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncEOSDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncJunosDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}
@app.get("/bad1")
@@ -48,19 +48,19 @@ def bad2(cmd: str):
}
driver = NXOSDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = IOSXRDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = IOSXEDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = EOSDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = JunosDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
dev_connect = {
"host": "65.65.65.65",
@@ -71,7 +71,7 @@ def bad2(cmd: str):
"platform": "cisco_iosxe",
}
with Scrapli(**dev_connect) as conn:
result = conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
result = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
dev_connect = {
"host": "65.65.65.65",
@@ -81,5 +81,5 @@ def bad2(cmd: str):
"transport": "ssh2",
}
with GenericDriver(**dev_connect) as conn:
result = conn.send_command(cmd) # $ result=BAD getSecondaryCommand=cmd
result = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}

View File

@@ -13,7 +13,7 @@ app = FastAPI()
async def bad1(cmd: bytes):
endpoint = SSHCommandClientEndpoint.newConnection(
reactor,
cmd, # $ result=BAD getSecondaryCommand=cmd
cmd, # $ result=BAD getRemoteCommand=cmd
b"username",
b"ssh.example.com",
22,
@@ -21,7 +21,7 @@ async def bad1(cmd: bytes):
SSHCommandClientEndpoint.existingConnection(
endpoint,
cmd) # $ result=BAD getSecondaryCommand=cmd
cmd) # $ result=BAD getRemoteCommand=cmd
factory = Factory()
d = endpoint.connect(factory)

View File

@@ -13,10 +13,10 @@ app = FastAPI()
@app.get("/bad1")
async def bad1(cmd: str):
stdin, stdout, stderr = paramiko_ssh_client.exec_command(cmd) # $ result=BAD getSecondaryCommand=cmd
stdin, stdout, stderr = paramiko_ssh_client.exec_command(cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}
@app.get("/bad2")
async def bad2(cmd: str):
stdin, stdout, stderr = paramiko_ssh_client.exec_command(command=cmd) # $ result=BAD getSecondaryCommand=cmd
stdin, stdout, stderr = paramiko_ssh_client.exec_command(command=cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}

View File

@@ -14,7 +14,7 @@ session.userauth_password("user", "password")
@app.get("/bad1")
async def bad1(cmd: str):
channel = session.open_session()
channel.execute(cmd) # $ result=BAD getSecondaryCommand=cmd
channel.execute(cmd) # $ result=BAD getRemoteCommand=cmd
channel.wait_eof()
channel.close()
channel.wait_closed()

View File

@@ -1,4 +0,0 @@
import python
import TestUtilities.dataflow.DataflowQueryTest
import experimental.semmle.python.security.SecondaryServerCmdInjection
import FromTaintTrackingConfig<SecondaryCommandInjectionConfig>

View File

@@ -1 +0,0 @@
experimental/Security/CWE-074/secondaryCommandInjection/SecondaryServerCmdInjection.ql