|
| 1 | +/** |
| 2 | + * @name Insecure basic authentication |
| 3 | + * @description Basic authentication only obfuscates username/password in Base64 encoding, which can be easily recognized and reversed. Transmission of sensitive information not over HTTPS is vulnerable to packet sniffing. |
| 4 | + * @kind path-problem |
| 5 | + * @id java/insecure-basic-auth |
| 6 | + * @tags security |
| 7 | + * external/cwe-522 |
| 8 | + * external/cwe-319 |
| 9 | + */ |
| 10 | + |
| 11 | +import java |
| 12 | +import semmle.code.java.frameworks.Networking |
| 13 | +import semmle.code.java.dataflow.TaintTracking |
| 14 | +import DataFlow::PathGraph |
| 15 | + |
| 16 | +/** |
| 17 | + * Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary. |
| 18 | + */ |
| 19 | +private string getPrivateHostRegex() { |
| 20 | + result = |
| 21 | + "(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?" |
| 22 | +} |
| 23 | + |
| 24 | +/** |
| 25 | + * The Java class `org.apache.http.client.methods.HttpRequestBase`. Popular subclasses include `HttpGet`, `HttpPost`, and `HttpPut`. |
| 26 | + * And the Java class `org.apache.http.message.BasicHttpRequest`. |
| 27 | + */ |
| 28 | +class ApacheHttpRequest extends RefType { |
| 29 | + ApacheHttpRequest() { |
| 30 | + this |
| 31 | + .getASourceSupertype*() |
| 32 | + .hasQualifiedName("org.apache.http.client.methods", "HttpRequestBase") or |
| 33 | + this.getASourceSupertype*().hasQualifiedName("org.apache.http.message", "BasicHttpRequest") |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +/** |
| 38 | + * Class of Java URL constructor. |
| 39 | + */ |
| 40 | +class URLConstructor extends ClassInstanceExpr { |
| 41 | + URLConstructor() { this.getConstructor().getDeclaringType() instanceof TypeUrl } |
| 42 | + |
| 43 | + predicate hasHttpStringArg() { |
| 44 | + this.getConstructor().getParameter(0).getType() instanceof TypeString and |
| 45 | + ( |
| 46 | + // URLs constructed with any of the three string constructors below: |
| 47 | + // `URL(String protocol, String host, int port, String file)`, |
| 48 | + // `URL(String protocol, String host, int port, String file, URLStreamHandler handler)`, |
| 49 | + // `URL(String protocol, String host, String file)` |
| 50 | + this.getConstructor().getNumberOfParameters() > 1 and |
| 51 | + concatHttpString(getArgument(0), this.getArgument(1)) // First argument contains the protocol part and the second argument contains the host part. |
| 52 | + or |
| 53 | + // URLs constructed with the string constructor `URL(String spec)` |
| 54 | + this.getConstructor().getNumberOfParameters() = 1 and |
| 55 | + this.getArgument(0) instanceof HttpString // First argument contains the whole spec. |
| 56 | + ) |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +/** |
| 61 | + * Class of Java URI constructor. |
| 62 | + */ |
| 63 | +class URIConstructor extends ClassInstanceExpr { |
| 64 | + URIConstructor() { this.getConstructor().getDeclaringType().hasQualifiedName("java.net", "URI") } |
| 65 | + |
| 66 | + predicate hasHttpStringArg() { |
| 67 | + ( |
| 68 | + this.getNumArgument() = 1 and |
| 69 | + this.getArgument(0) instanceof HttpString // `URI(String str)` |
| 70 | + or |
| 71 | + this.getNumArgument() = 4 and |
| 72 | + concatHttpString(this.getArgument(0), this.getArgument(1)) // `URI(String scheme, String host, String path, String fragment)` |
| 73 | + or |
| 74 | + this.getNumArgument() = 5 and |
| 75 | + concatHttpString(this.getArgument(0), this.getArgument(1)) // `URI(String scheme, String authority, String path, String query, String fragment)` without user-info in authority |
| 76 | + or |
| 77 | + this.getNumArgument() = 7 and |
| 78 | + concatHttpString(this.getArgument(0), this.getArgument(2)) // `URI(String scheme, String userInfo, String host, int port, String path, String query, String fragment)` |
| 79 | + ) |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +/** |
| 84 | + * String of HTTP URLs not in private domains. |
| 85 | + */ |
| 86 | +class HttpStringLiteral extends StringLiteral { |
| 87 | + HttpStringLiteral() { |
| 88 | + // Match URLs with the HTTP protocol and without private IP addresses to reduce false positives. |
| 89 | + exists(string s | this.getRepresentedString() = s | |
| 90 | + s.regexpMatch("(?i)http://[\\[a-zA-Z0-9].*") and |
| 91 | + not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex()) |
| 92 | + ) |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +/** |
| 97 | + * Checks both parts of protocol and host. |
| 98 | + */ |
| 99 | +predicate concatHttpString(Expr protocol, Expr host) { |
| 100 | + ( |
| 101 | + protocol.(CompileTimeConstantExpr).getStringValue().regexpMatch("(?i)http(://)?") or |
| 102 | + protocol |
| 103 | + .(VarAccess) |
| 104 | + .getVariable() |
| 105 | + .getAnAssignedValue() |
| 106 | + .(CompileTimeConstantExpr) |
| 107 | + .getStringValue() |
| 108 | + .regexpMatch("(?i)http(://)?") |
| 109 | + ) and |
| 110 | + not exists(string hostString | |
| 111 | + hostString = host.(CompileTimeConstantExpr).getStringValue() or |
| 112 | + hostString = |
| 113 | + host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue() |
| 114 | + | |
| 115 | + hostString.length() = 0 or // Empty host is loopback address |
| 116 | + hostString.regexpMatch(getPrivateHostRegex()) |
| 117 | + ) |
| 118 | +} |
| 119 | + |
| 120 | +/** Gets the leftmost operand in a concatenated string */ |
| 121 | +Expr getLeftmostConcatOperand(Expr expr) { |
| 122 | + if expr instanceof AddExpr |
| 123 | + then result = getLeftmostConcatOperand(expr.(AddExpr).getLeftOperand()) |
| 124 | + else result = expr |
| 125 | +} |
| 126 | + |
| 127 | +/** |
| 128 | + * String concatenated with `HttpStringLiteral`. |
| 129 | + */ |
| 130 | +class HttpString extends Expr { |
| 131 | + HttpString() { |
| 132 | + this instanceof HttpStringLiteral |
| 133 | + or |
| 134 | + concatHttpString(this.(AddExpr).getLeftOperand(), |
| 135 | + getLeftmostConcatOperand(this.(AddExpr).getRightOperand())) |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +/** |
| 140 | + * String pattern of basic authentication. |
| 141 | + */ |
| 142 | +class BasicAuthString extends StringLiteral { |
| 143 | + BasicAuthString() { exists(string s | this.getRepresentedString() = s | s.matches("Basic %")) } |
| 144 | +} |
| 145 | + |
| 146 | +/** |
| 147 | + * String concatenated with `BasicAuthString`. |
| 148 | + */ |
| 149 | +predicate builtFromBasicAuthStringConcat(Expr expr) { |
| 150 | + expr instanceof BasicAuthString |
| 151 | + or |
| 152 | + builtFromBasicAuthStringConcat(expr.(AddExpr).getLeftOperand()) |
| 153 | + or |
| 154 | + exists(Expr other | builtFromBasicAuthStringConcat(other) | |
| 155 | + exists(Variable var | var.getAnAssignedValue() = other and var.getAnAccess() = expr) |
| 156 | + ) |
| 157 | +} |
| 158 | + |
| 159 | +/** The `openConnection` method of Java URL. Not to include `openStream` since it won't be used in this query. */ |
| 160 | +class HttpURLOpenMethod extends Method { |
| 161 | + HttpURLOpenMethod() { |
| 162 | + this.getDeclaringType() instanceof TypeUrl and |
| 163 | + this.getName() = "openConnection" |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +/** Constructor of `ApacheHttpRequest` */ |
| 168 | +predicate apacheHttpRequest(DataFlow::Node node1, DataFlow::Node node2) { |
| 169 | + exists(ConstructorCall cc | |
| 170 | + cc.getConstructedType() instanceof ApacheHttpRequest and |
| 171 | + node2.asExpr() = cc and |
| 172 | + cc.getAnArgument() = node1.asExpr() |
| 173 | + ) |
| 174 | +} |
| 175 | + |
| 176 | +/** `URI` methods */ |
| 177 | +predicate createURI(DataFlow::Node node1, DataFlow::Node node2) { |
| 178 | + exists( |
| 179 | + URIConstructor cc // new URI |
| 180 | + | |
| 181 | + node2.asExpr() = cc and |
| 182 | + cc.getArgument(0) = node1.asExpr() |
| 183 | + ) |
| 184 | + or |
| 185 | + exists( |
| 186 | + StaticMethodAccess ma // URI.create |
| 187 | + | |
| 188 | + ma.getMethod().getDeclaringType().hasQualifiedName("java.net", "URI") and |
| 189 | + ma.getMethod().hasName("create") and |
| 190 | + node1.asExpr() = ma.getArgument(0) and |
| 191 | + node2.asExpr() = ma |
| 192 | + ) |
| 193 | +} |
| 194 | + |
| 195 | +/** Constructors of `URL` */ |
| 196 | +predicate createURL(DataFlow::Node node1, DataFlow::Node node2) { |
| 197 | + exists(URLConstructor cc | |
| 198 | + node2.asExpr() = cc and |
| 199 | + cc.getArgument(0) = node1.asExpr() |
| 200 | + ) |
| 201 | +} |
| 202 | + |
| 203 | +/** Method call of `HttpURLOpenMethod` */ |
| 204 | +predicate urlOpen(DataFlow::Node node1, DataFlow::Node node2) { |
| 205 | + exists(MethodAccess ma | |
| 206 | + ma.getMethod() instanceof HttpURLOpenMethod and |
| 207 | + node1.asExpr() = ma.getQualifier() and |
| 208 | + ma = node2.asExpr() |
| 209 | + ) |
| 210 | +} |
| 211 | + |
| 212 | +/** Constructor of `BasicRequestLine` */ |
| 213 | +predicate basicRequestLine(DataFlow::Node node1, DataFlow::Node node2) { |
| 214 | + exists(ConstructorCall mcc | |
| 215 | + mcc.getConstructedType().hasQualifiedName("org.apache.http.message", "BasicRequestLine") and |
| 216 | + mcc.getArgument(1) = node1.asExpr() and // `BasicRequestLine(String method, String uri, ProtocolVersion version) |
| 217 | + node2.asExpr() = mcc |
| 218 | + ) |
| 219 | +} |
| 220 | + |
| 221 | +class BasicAuthFlowConfig extends TaintTracking::Configuration { |
| 222 | + BasicAuthFlowConfig() { this = "InsecureBasicAuth::BasicAuthFlowConfig" } |
| 223 | + |
| 224 | + override predicate isSource(DataFlow::Node src) { |
| 225 | + src.asExpr() instanceof HttpString |
| 226 | + or |
| 227 | + exists(URLConstructor uc | |
| 228 | + uc.hasHttpStringArg() and |
| 229 | + src.asExpr() = uc.getArgument(0) |
| 230 | + ) |
| 231 | + or |
| 232 | + exists(URIConstructor uc | |
| 233 | + uc.hasHttpStringArg() and |
| 234 | + src.asExpr() = uc.getArgument(0) |
| 235 | + ) |
| 236 | + } |
| 237 | + |
| 238 | + override predicate isSink(DataFlow::Node sink) { |
| 239 | + exists(MethodAccess ma | |
| 240 | + sink.asExpr() = ma.getQualifier() and |
| 241 | + ( |
| 242 | + ma.getMethod().hasName("addHeader") or |
| 243 | + ma.getMethod().hasName("setHeader") or |
| 244 | + ma.getMethod().hasName("setRequestProperty") |
| 245 | + ) and |
| 246 | + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "Authorization" and |
| 247 | + builtFromBasicAuthStringConcat(ma.getArgument(1)) |
| 248 | + ) |
| 249 | + } |
| 250 | + |
| 251 | + override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { |
| 252 | + apacheHttpRequest(node1, node2) or |
| 253 | + createURI(node1, node2) or |
| 254 | + basicRequestLine(node1, node2) or |
| 255 | + createURL(node1, node2) or |
| 256 | + urlOpen(node1, node2) |
| 257 | + } |
| 258 | +} |
| 259 | + |
| 260 | +from DataFlow::PathNode source, DataFlow::PathNode sink, BasicAuthFlowConfig config |
| 261 | +where config.hasFlowPath(source, sink) |
| 262 | +select sink.getNode(), source, sink, "Insecure basic authentication from $@.", source.getNode(), |
| 263 | + "HTTP url" |
0 commit comments