[Java] CWE-078: Add JSch OS command injection sink

This commit is contained in:
p0wn4j
2021-03-24 00:08:39 +04:00
parent a7030c7fed
commit 3d891f0b39
15 changed files with 487 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.ExternalProcess
import semmle.code.java.security.CommandArguments
private class RemoteUserInputToArgumentToExecFlowConfig extends TaintTracking::Configuration {
RemoteUserInputToArgumentToExecFlowConfig() {
this = "ExecCommon::RemoteUserInputToArgumentToExecFlowConfig"
}
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof ArgumentToExec }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType
or
node.getType() instanceof BoxedType
or
isSafeCommandArgument(node.asExpr())
}
}
/**
* Implementation of `ExecTainted.ql`. It is extracted to a QLL
* so that it can be excluded from `ExecUnescaped.ql` to avoid
* reporting overlapping results.
*/
predicate execTainted(DataFlow::PathNode source, DataFlow::PathNode sink, ArgumentToExec execArg) {
exists(RemoteUserInputToArgumentToExecFlowConfig conf |
conf.hasFlowPath(source, sink) and sink.getNode() = DataFlow::exprNode(execArg)
)
}

View File

@@ -0,0 +1,24 @@
/**
* @name Uncontrolled command line
* @description Using externally controlled strings in a command line is vulnerable to malicious
* changes in the strings.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/command-line-injection
* @tags security
* external/cwe/cwe-078
* external/cwe/cwe-088
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.ExternalProcess
import ExecCommon
import JSchOSInjection
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, ArgumentToExec execArg
where execTainted(source, sink, execArg)
select execArg, source, sink, "$@ flows to here and is used in a command.", source.getNode(),
"User-provided value"

View File

@@ -0,0 +1,64 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
JSch is a pure Java implementation of SSH2.
JSch allows you to connect to a sshd server and use port forwarding, X11 forwarding,
file transfer, command execution, etc., and you can integrate its functionality into your own Java programs.
JSch is licensed under BSD style license.
If an OS command is built using an attacker-controlled data, it may allow the attacker
to run an arbitrary code on the remote host.
</p>
</overview>
<recommendation>
<p>
Including an user input in a JSch lib's OS command expression should be avoided.
If this is not possible, then a strong input validation must be performed.
Some examples of an effective validation include:
Validating against an allowlist of permitted values.
Validating that the input is a number.
Validating that the input contains only alphanumeric characters, no other syntax or whitespace.
Never attempt to sanitize input by escaping shell metacharacters. In practice,
this is just too error-prone and vulnerable to being bypassed by a skilled attacker.
</p>
</recommendation>
<example>
<p>
The following example uses the untrusted data to build an OS command.
</p>
<sample src="JSchOSInjectionBad.java" />
</example>
<example>
<p>
The following example validates the untrusted data before build an OS command.
</p>
<sample src="JSchOSInjectionSanitized.java" />
</example>
<references>
<li>
JCraft:
<a href="http://www.jcraft.com/jsch/">JSch - Java Secure Channel</a>.
</li>
<li>
OWASP:
<a href="https://owasp.org/www-community/attacks/Command_Injection">Command Injection</a>.
</li>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html">OS Command Injection Defense Cheat Sheet</a>.
</li>
<li>
PortSwigger:
<a href="https://portswigger.net/web-security/os-command-injection">OS command injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* Provides classes for JSch OS command injection detection
*/
import java
/** The class `com.jcraft.jsch.ChannelExec`. */
private class JSchChannelExec extends RefType {
JSchChannelExec() { this.hasQualifiedName("com.jcraft.jsch", "ChannelExec") }
}
/** A method to set an OS Command for the execution. */
private class ChannelExecSetCommandMethod extends Method, ExecCallable {
ChannelExecSetCommandMethod() {
this.hasName("setCommand") and
this.getDeclaringType() instanceof JSchChannelExec
}
override int getAnExecutedArgument() { result = 0 }
}

View File

@@ -0,0 +1,17 @@
public class JSchOSInjectionBad {
void jschOsExecution(HttpServletRequest request) {
String command = request.getParameter("command");
JSch jsch = new JSch();
Session session = jsch.getSession("user", "sshHost", 22);
session.setPassword("password");
session.connect();
Channel channel = session.openChannel("exec");
// BAD - untrusted user data is used directly in a command
((ChannelExec) channel).setCommand("ping " + command);
channel.connect();
}
}

View File

@@ -0,0 +1,46 @@
public class JSchOSInjectionSanitized {
void jschOsExecutionPing(HttpServletRequest request) {
String untrusted = request.getParameter("command");
//GOOD - Validate user the input.
if (!com.google.common.net.InetAddresses.isInetAddress(untrusted)) {
System.out.println("Invalid IP address");
return;
}
JSch jsch = new JSch();
Session session = jsch.getSession("user", "host", 22);
session.setPassword("password");
session.connect();
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand("ping " + untrusted);
channel.connect();
}
void jschOsExecutionDig(HttpServletRequest request) {
String untrusted = request.getParameter("command");
//GOOD - check whether the user input doesn't contain dangerous shell characters.
String[] badChars = new String[] {"^", "~" ," " , "&", "|", ";", "$", ">", "<", "`", "\\", ",", "!", "{", "}", "(", ")", "@", "%", "#", "%0A", "%0a", "\n", "\r\n"};
for (String badChar : badChars) {
if (untrusted.contains(badChar)) {
System.out.println("Invalid IP address");
return;
}
}
JSch jsch = new JSch();
Session session = jsch.getSession("user", "host", 22);
session.setPassword("password");
session.connect();
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand("dig " + untrusted);
channel.connect();
}
}

View File

@@ -0,0 +1,11 @@
edges
| JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) : String | JSchOSInjectionTest.java:26:48:26:64 | ... + ... |
| JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) : String | JSchOSInjectionTest.java:50:32:50:48 | ... + ... |
nodes
| JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| JSchOSInjectionTest.java:26:48:26:64 | ... + ... | semmle.label | ... + ... |
| JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| JSchOSInjectionTest.java:50:32:50:48 | ... + ... | semmle.label | ... + ... |
#select
| JSchOSInjectionTest.java:26:48:26:64 | ... + ... | JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) : String | JSchOSInjectionTest.java:26:48:26:64 | ... + ... | $@ flows to here and is used in a command. | JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) | User-provided value |
| JSchOSInjectionTest.java:50:32:50:48 | ... + ... | JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) : String | JSchOSInjectionTest.java:50:32:50:48 | ... + ... | $@ flows to here and is used in a command. | JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) | User-provided value |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-078/ExecTainted.ql

View File

@@ -0,0 +1,56 @@
import com.jcraft.jsch.*;
import javax.servlet.http.*;
import javax.servlet.ServletException;
import java.io.IOException;
public class JSchOSInjectionTest extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String host = "sshHost";
String user = "user";
String password = "password";
String command = request.getParameter("command");
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
JSch jsch = new JSch();
Session session = jsch.getSession(user, host, 22);
session.setPassword(password);
session.setConfig(config);
session.connect();
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand("ping " + command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
channel.connect();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String host = "sshHost";
String user = "user";
String password = "password";
String command = request.getParameter("command");
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
JSch jsch = new JSch();
Session session = jsch.getSession(user, host, 22);
session.setPassword(password);
session.setConfig(config);
session.connect();
ChannelExec channel = (ChannelExec)session.openChannel("exec");
channel.setCommand("ping " + command);
channel.setInputStream(null);
channel.setErrStream(System.err);
channel.connect();
}
}

View File

@@ -0,0 +1,2 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/jsch-0.1.55

View File

@@ -0,0 +1,46 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jsch;
import java.io.*;
public abstract class Channel implements Runnable {
public void connect() {
}
public void setInputStream(InputStream in){
}
public void disconnect(){
}
public void run() {
}
}

View File

@@ -0,0 +1,40 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jsch;
import java.io.*;
public class ChannelExec extends ChannelSession {
public void setErrStream(OutputStream out) {
}
public void setCommand(String command){
}
}

View File

@@ -0,0 +1,33 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jsch;
class ChannelSession extends Channel {
}

View File

@@ -0,0 +1,40 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jsch;
public class JSch {
public JSch() {}
public Session getSession(String username, String host, int port) {
return null;
}
}

View File

@@ -0,0 +1,55 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jsch;
public class Session implements Runnable {
public Channel openChannel(String type) {
if ("exec".equals(type))
return new ChannelExec();
return null;
}
public void setPassword(String password) {
}
public void setConfig(java.util.Properties newconf) {
}
public void connect() {
}
public void run(){
}
public void disconnect(){
}
}