Skip to content

Commit 43d1bc8

Browse files
authored
SONARPY-1552 Rule S5332: Raise an issue on server_bind calls. (#1640)
1 parent 2f12f0a commit 43d1bc8

File tree

2 files changed

+33
-32
lines changed

2 files changed

+33
-32
lines changed

python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.net.URI;
2323
import java.net.URISyntaxException;
2424
import java.util.Arrays;
25-
import java.util.HashMap;
2625
import java.util.List;
2726
import java.util.Map;
2827
import java.util.Objects;
@@ -41,7 +40,6 @@
4140
import org.sonar.plugins.python.api.tree.CallExpression;
4241
import org.sonar.plugins.python.api.tree.ClassDef;
4342
import org.sonar.plugins.python.api.tree.Expression;
44-
import org.sonar.plugins.python.api.tree.FunctionDef;
4543
import org.sonar.plugins.python.api.tree.HasSymbol;
4644
import org.sonar.plugins.python.api.tree.Name;
4745
import org.sonar.plugins.python.api.tree.QualifiedExpression;
@@ -56,17 +54,15 @@
5654
public class ClearTextProtocolsCheck extends PythonSubscriptionCheck {
5755
private static final List<String> SENSITIVE_PROTOCOLS = Arrays.asList("http://", "ftp://", "telnet://");
5856
private static final Pattern LOOPBACK = Pattern.compile("localhost|127(?:\\.[0-9]+){0,2}\\.[0-9]+$|^(?:0*\\:)*?:?0*1", Pattern.CASE_INSENSITIVE);
59-
private static final Map<String, String> ALTERNATIVES = new HashMap<>();
60-
private static final String SENSITIVE_HTTP_SERVER_CALL = "socketserver.BaseServer.serve_forever";
57+
private static final Map<String, String> ALTERNATIVES = Map.of(
58+
"http", "https",
59+
"ftp", "sftp, scp or ftps",
60+
"telnet", "ssh");
61+
private static final String SENSITIVE_HTTP_SERVER_START_FQN = "socketserver.BaseServer.serve_forever";
62+
private static final String SENSITIVE_HTTP_SERVER_BIND_FQN = "socketserver.BaseServer.server_bind";
63+
private static final Set<String> SENSITIVE_HTTP_SERVER_METHOD_NAMES = Set.of("serve_forever", "server_bind");
6164
private static final Set<String> SENSITIVE_HTTP_SERVER_CLASSES = Set.of("http.server.HTTPServer", "http.server.ThreadingHTTPServer");
6265

63-
64-
static {
65-
ALTERNATIVES.put("http", "https");
66-
ALTERNATIVES.put("ftp", "sftp, scp or ftps");
67-
ALTERNATIVES.put("telnet", "ssh");
68-
}
69-
7066
@Override
7167
public void initialize(Context context) {
7268
context.registerSyntaxNodeConsumer(Tree.Kind.STRING_ELEMENT, ctx -> {
@@ -89,28 +85,27 @@ public void initialize(Context context) {
8985

9086
context.registerSyntaxNodeConsumer(Tree.Kind.QUALIFIED_EXPR, ClearTextProtocolsCheck::checkServerCallFromSuper);
9187

92-
context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, ClearTextProtocolsCheck::checkServerBindOverrides);
88+
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ClearTextProtocolsCheck::checkServerBindCalls);
89+
9390
new ClearTextProtocolsCheckPart().initialize(context);
9491
}
9592

9693
private static void checkServerCallFromSuper(SubscriptionContext ctx) {
9794
QualifiedExpression qualifiedExpression = (QualifiedExpression) ctx.syntaxNode();
9895
Optional.of(qualifiedExpression)
99-
.filter(qe -> isName("serve_forever", qe.name()) && isCallToSensitiveSuperClass(qe))
96+
.filter(qe -> SENSITIVE_HTTP_SERVER_METHOD_NAMES.contains(qe.name().name()))
97+
.filter(ClearTextProtocolsCheck::isCallToSensitiveSuperClass)
10098
.map(qe -> TreeUtils.firstAncestorOfKind(qe, Tree.Kind.CALL_EXPR))
10199
.flatMap(TreeUtils.toOptionalInstanceOfMapper(CallExpression.class))
102100
.ifPresent(ce -> ctx.addIssue(ce, message("http")));
103101
}
104102

105-
private static void checkServerBindOverrides(SubscriptionContext ctx) {
106-
FunctionDef funcDef = (FunctionDef) ctx.syntaxNode();
107-
Optional.of(funcDef)
108-
.filter(fd -> isName("server_bind", fd.name()) && isParentClassExtendingSensitiveClass(funcDef))
109-
.ifPresent(fd -> ctx.addIssue(fd.defKeyword(), fd.rightPar(), message("http")));
110-
}
111-
112-
private static boolean isName(String nameToCheck, Name name) {
113-
return nameToCheck.equals(name.name());
103+
private static void checkServerBindCalls(SubscriptionContext ctx) {
104+
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
105+
Optional.ofNullable(callExpression.calleeSymbol())
106+
.map(Symbol::fullyQualifiedName)
107+
.filter(fqn -> SENSITIVE_HTTP_SERVER_BIND_FQN.equals(fqn) && isParentClassExtendingSensitiveClass(callExpression))
108+
.ifPresent(fqn -> ctx.addIssue(callExpression, message("http")));
114109
}
115110

116111
private static boolean isCallToSensitiveSuperClass(QualifiedExpression expression) {
@@ -205,7 +200,7 @@ private static Optional<String> isUnsafeLib(String qualifiedName) {
205200
if ("ftplib.FTP".equals(qualifiedName)) {
206201
return Optional.of("ftp");
207202
}
208-
if (SENSITIVE_HTTP_SERVER_CALL.equals(qualifiedName)) {
203+
if (SENSITIVE_HTTP_SERVER_START_FQN.equals(qualifiedName)) {
209204
return Optional.of("http");
210205
}
211206
return Optional.empty();

python-checks/src/test/resources/checks/hotspots/clearTextProtocols.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,32 +156,38 @@ def python_web_server_noncompliant():
156156

157157
class MyServer(HTTPServer):
158158
def run(self):
159+
HTTPServer.server_bind(self) # Noncompliant
160+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
159161
super(self).serve_forever() # Noncompliant
160162
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^
161163

162-
def server_bind(self): # Noncompliant
163-
# ^^^^^^^^^^^^^^^^^^^^^
164-
pass
165-
166164
my_server = MyServer(('0.0.0.0', 8080), SimpleHTTPRequestHandler)
167165
my_server.serve_forever() # Noncompliant
168166
# ^^^^^^^^^^^^^^^^^^^^^^^^^
169167

170168
class MyThreadingServer(ThreadingHTTPServer):
171169
def run(self):
170+
super().server_bind() # Noncompliant
171+
# ^^^^^^^^^^^^^^^^^^^^^
172172
super(self).serve_forever() # Noncompliant
173173
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^
174174

175-
def server_bind(self): # Noncompliant
176-
# ^^^^^^^^^^^^^^^^^^^^^
177-
pass
178-
179175
my_threading_server = MyThreadingServer(('0.0.0.0', 8080), SimpleHTTPRequestHandler)
180176
my_threading_server.serve_forever() # Noncompliant
181177

182178

183179
def python_web_server_compliant(ok_server):
184180

181+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
182+
from http.server import HTTPServer as PythonServer
183+
184+
http_server = PythonServer(('0.0.0.0', 8080), SimpleHTTPRequestHandler)
185+
http_server.server_bind() # Compliant we do not raise here as the call to server bind was done already in the constructor
186+
187+
class MyThreadingServer(ThreadingHTTPServer):
188+
def server_bind(self): # Compliant
189+
pass
190+
185191
ok_server.serve_forever() # Compliant
186192
ok_server.server_bind()
187193

@@ -197,7 +203,7 @@ def run(self):
197203
super(self).serve_forever() # Compliant
198204

199205
def server_bind(self):
200-
pass
206+
HTTPServer.server_bind(self)
201207

202208
my_server = MyServer()
203209
my_server.serve_forever()

0 commit comments

Comments
 (0)