Merge branch 'main' into criemen/pytest-csharp

This commit is contained in:
Cornelius Riemenschneider
2024-08-07 18:34:46 +02:00
committed by GitHub
61 changed files with 8888 additions and 549 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Add unique constraint on preprocessor directive and compilation pairs
compatibility: backwards

View File

@@ -20,6 +20,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(EscapingTextWriter trapFile)
{
trapFile.WriteSubId(Context.CreateLocation(ReportingLocation));
trapFile.WriteSubId(start);
trapFile.Write(Symbol.IsActive);
trapFile.Write(',');
trapFile.Write(Symbol.BranchTaken);

View File

@@ -18,6 +18,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(EscapingTextWriter trapFile)
{
trapFile.WriteSubId(Context.CreateLocation(ReportingLocation));
trapFile.WriteSubId(start);
trapFile.Write(Symbol.IsActive);
trapFile.Write(',');
trapFile.Write(Symbol.BranchTaken);

View File

@@ -13,6 +13,14 @@ namespace Semmle.Extraction.CSharp.Entities
this.start = start;
}
public override void WriteId(EscapingTextWriter trapFile)
{
trapFile.WriteSubId(Context.CreateLocation(ReportingLocation));
trapFile.WriteSubId(start);
trapFile.Write(Symbol.IsActive);
trapFile.Write(";trivia");
}
protected override void PopulatePreprocessor(TextWriter trapFile)
{
trapFile.directive_endifs(this, start);

View File

@@ -190,37 +190,29 @@ namespace Semmle.Extraction.CSharp
var transformedSourcePath = PathTransformer.Transform(sourcePath);
var trapPath = transformedSourcePath.GetTrapPath(Logger, options.TrapCompression);
var upToDate = false;
// compilation.Clone() is used to allow symbols to be garbage collected.
using var trapWriter = transformedSourcePath.CreateTrapWriter(Logger, options.TrapCompression, discardDuplicates: false);
upToDate = FileIsUpToDate(sourcePath, trapWriter.TrapFile);
var currentTaskId = IncrementTaskCount();
ReportProgressTaskStarted(currentTaskId, sourcePath);
if (!upToDate)
var cx = new Context(ExtractionContext, compilation, trapWriter, new SourceScope(tree), addAssemblyTrapPrefix);
// Ensure that the file itself is populated in case the source file is totally empty
var root = tree.GetRoot();
Entities.File.Create(cx, root.SyntaxTree.FilePath);
var csNode = (CSharpSyntaxNode)root;
var directiveVisitor = new DirectiveVisitor(cx);
csNode.Accept(directiveVisitor);
foreach (var branch in directiveVisitor.BranchesTaken)
{
var cx = new Context(ExtractionContext, compilation, trapWriter, new SourceScope(tree), addAssemblyTrapPrefix);
// Ensure that the file itself is populated in case the source file is totally empty
var root = tree.GetRoot();
Entities.File.Create(cx, root.SyntaxTree.FilePath);
var csNode = (CSharpSyntaxNode)root;
var directiveVisitor = new DirectiveVisitor(cx);
csNode.Accept(directiveVisitor);
foreach (var branch in directiveVisitor.BranchesTaken)
{
cx.TrapStackSuffix.Add(branch);
}
csNode.Accept(new CompilationUnitVisitor(cx));
cx.PopulateAll();
CommentPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
cx.PopulateAll();
cx.TrapStackSuffix.Add(branch);
}
csNode.Accept(new CompilationUnitVisitor(cx));
cx.PopulateAll();
CommentPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
cx.PopulateAll();
ReportProgressTaskDone(currentTaskId, sourcePath, trapPath, stopwatch.Elapsed, upToDate ? AnalysisAction.UpToDate : AnalysisAction.Extracted);
ReportProgressTaskDone(currentTaskId, sourcePath, trapPath, stopwatch.Elapsed, AnalysisAction.Extracted);
}
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
{
@@ -268,12 +260,6 @@ namespace Semmle.Extraction.CSharp
extractionTasks.Add(() => DoAnalyseCompilation());
}
private static bool FileIsUpToDate(string src, string dest)
{
return File.Exists(dest) &&
File.GetLastWriteTime(dest) >= File.GetLastWriteTime(src);
}
private static void AnalyseNamespace(Context cx, INamespaceSymbol ns)
{
foreach (var memberNamespace in ns.GetNamespaceMembers())

View File

@@ -194,8 +194,11 @@ namespace Semmle.Extraction
var hash = FileUtils.ComputeFileHash(tmpFile);
if (existingHash != hash)
{
var root = TrapFile.Substring(0, TrapFile.Length - 8); // Remove trailing ".trap.gz"
if (TryMove(tmpFile, $"{root}-{hash}.trap{TrapExtension(trapCompression)}"))
var extension = TrapExtension(trapCompression);
var root = TrapFile[..^extension.Length]; // Remove trailing ".trap", ".trap.gz", or ".trap.br"
var newTrapName = $"{root}-{hash}{extension}";
logger.LogInfo($"Identical trap file for {TrapFile} already exists, renaming to {newTrapName}");
if (TryMove(tmpFile, $"{newTrapName}"))
return;
}
logger.LogInfo($"Identical trap file for {TrapFile} already exists");
@@ -217,16 +220,16 @@ namespace Semmle.Extraction
{
switch (compression)
{
case CompressionMode.None: return "";
case CompressionMode.Gzip: return ".gz";
case CompressionMode.Brotli: return ".br";
case CompressionMode.None: return ".trap";
case CompressionMode.Gzip: return ".trap.gz";
case CompressionMode.Brotli: return ".trap.br";
default: throw new ArgumentOutOfRangeException(nameof(compression), compression, "Unsupported compression type");
}
}
public static string TrapPath(ILogger logger, string? folder, PathTransformer.ITransformedPath path, TrapWriter.CompressionMode trapCompression)
{
var filename = $"{path.Value}.trap{TrapExtension(trapCompression)}";
var filename = $"{path.Value}{TrapExtension(trapCompression)}";
if (string.IsNullOrEmpty(folder))
folder = Directory.GetCurrentDirectory();

View File

@@ -0,0 +1,4 @@
| Program.cs:3:24:3:27 | Main | 2 |
| Program.cs:10:17:10:33 | GreetConditional1 | 2 |
| Program.cs:19:17:19:21 | Greet | 2 |
| Program.cs:25:17:25:33 | GreetConditional2 | 2 |

View File

@@ -0,0 +1,5 @@
import csharp
from Method m
where m.fromSource()
select m, count(m.getBody())

View File

@@ -0,0 +1,40 @@
public class Test
{
public static void Main()
{
Greet();
GreetConditional1();
GreetConditional2();
}
static void GreetConditional1()
{
#if A
Console.WriteLine("Hello, A!");
#elif C
Console.WriteLine("Hello, C!");
#endif
}
static void Greet()
{
Console.WriteLine("Hello, World!");
}
#if A
static void GreetConditional2()
{
Console.WriteLine("Hello, A!");
}
#elif B
static void GreetConditional2()
{
Console.WriteLine("Hello, B!");
}
#else
static void GreetConditional2()
{
Console.WriteLine("Hello, Others!");
}
#endif
}

View File

@@ -0,0 +1,5 @@
{
"sdk": {
"version": "8.0.101"
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="**/*.cs" />
<Compile Include="Program.cs" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
from create_database_utils import *
import os
os.environ["CODEQL_EXTRACTOR_CSHARP_OPTION_TRAP_COMPRESSION"] = "none"
run_codeql_database_create(['dotnet build /p:DefineConstants=A', 'dotnet build /p:DefineConstants=B'], lang="csharp")

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The extractor has been changed to not skip source files that have already been seen. This has an impact on source files that are compiled multiple times in the build process. Source files with conditional compilation preprocessor directives (such as `#if`) are now extracted for each set of preprocessor symbols that are used during the build process.

View File

@@ -421,7 +421,7 @@ preprocessor_directive_location(
int loc: @location ref);
preprocessor_directive_compilation(
unique int id: @preprocessor_directive ref,
int id: @preprocessor_directive ref,
int compilation: @compilation ref);
preprocessor_directive_active(

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Remove unique constraint on preprocessor directive and compilation pairs
compatibility: backwards

View File

@@ -83,13 +83,13 @@ java.io,66,1,256,,,,,,,,,22,,,,,,,,,,,,,,,44,,,,,,,,,,,,,,,,,,,,,,1,,249,7
java.lang,38,3,756,,13,,,,,,1,,,,,,,,,,,,8,,,,11,,,4,,,1,,,,,,,,,,,,,,,3,,,681,75
java.math,,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9
java.net,23,3,278,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,21,,,,,,,,,,,,,,3,274,4
java.nio,44,,361,,,,,,,,,5,,,,,,,,,,,,,,,38,,,,,,,,,1,,,,,,,,,,,,,,,259,102
java.nio,47,,361,,,,,,,,,5,,,,,,,,,,,,,,,41,,,,,,,,,1,,,,,,,,,,,,,,,259,102
java.rmi,,,71,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,71,
java.security,21,,543,,,11,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,539,4
java.sql,15,1,303,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,9,,,,,,,,,1,,,,303,
java.text,,,134,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,134,
java.time,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35,88
java.util,47,2,1218,,,,,,,,,1,,,,,,,,,,,34,,,,2,,,,5,2,,1,2,,,,,,,,,,,,,2,,,704,514
java.util,48,2,1218,,,,,,,,,1,,,,,,,,,,,34,,,,3,,,,5,2,,1,2,,,,,,,,,,,,,2,,,704,514
javafx.scene.web,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,
javax.accessibility,,,31,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,31,
javax.activation,2,,7,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,1,,,,,,,,,,,,,,,7,
@@ -138,6 +138,7 @@ jodd.json,,,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10
kotlin,16,,1849,,,,,,,,,,,,,,,,,,,,,,,,14,,,,,,,,,2,,,,,,,,,,,,,,,1836,13
liquibase.database.jvm,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,
liquibase.statement.core,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,
net.lingala.zip4j,2,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,
net.schmizz.sshj,4,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
net.sf.json,2,,338,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,321,17
net.sf.saxon.s9api,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,,,,
@@ -231,7 +232,7 @@ org.springframework.beans,,,30,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
org.springframework.boot.jdbc,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,
org.springframework.cache,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13
org.springframework.context,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,
org.springframework.core.io,3,,5,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,1,,,,,,,,,,,,,,,5,
org.springframework.core.io,17,,5,,,,,,,,,,,,,,,,,,,,,,,,16,,,,,,,,,1,,,,,,,,,,,,,,,5,
org.springframework.data.repository,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
org.springframework.http,14,,77,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,14,,,,,,,,,,,,,,,67,10
org.springframework.jdbc.core,19,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,19,,,,,,,,,,,,,,
@@ -242,7 +243,7 @@ org.springframework.ldap,47,,,,,,,,,,,,,,,,,,,33,,14,,,,,,,,,,,,,,,,,,,,,,,,,,,,
org.springframework.security.core.userdetails,2,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
org.springframework.security.web.savedrequest,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,
org.springframework.ui,,,32,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,32
org.springframework.util,3,,142,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,90,52
org.springframework.util,10,,142,,,,,,,,,,,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,,,,,,90,52
org.springframework.validation,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13,
org.springframework.web.client,13,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,,,,,,3,,
org.springframework.web.context.request,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,
@@ -268,6 +269,7 @@ ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,
ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10,
ratpack.util,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35
retrofit2,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,1,
software.amazon.awssdk.transfer.s3.model,8,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,
sun.awt,,,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,
sun.jvmstat.perfdata.monitor.protocol.local,3,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
sun.jvmstat.perfdata.monitor.protocol.rmi,1,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1 package sink source summary sink:bean-validation sink:command-injection sink:credentials-key sink:credentials-password sink:credentials-username sink:encryption-iv sink:encryption-salt sink:environment-injection sink:file-content-store sink:fragment-injection sink:groovy-injection sink:hostname-verification sink:html-injection sink:information-leak sink:intent-redirection sink:jexl-injection sink:jndi-injection sink:js-injection sink:ldap-injection sink:log-injection sink:mvel-injection sink:notification sink:ognl-injection sink:path-injection sink:pending-intents sink:regex-use sink:regex-use[-1] sink:regex-use[0] sink:regex-use[] sink:regex-use[f-1] sink:regex-use[f1] sink:regex-use[f] sink:request-forgery sink:response-splitting sink:sql-injection sink:template-injection sink:trust-boundary-violation sink:url-forward sink:url-redirection sink:xpath-injection sink:xslt-injection source:android-external-storage-dir source:contentprovider source:database source:environment source:file source:remote summary:taint summary:value
83 java.lang 38 3 756 13 1 8 11 4 1 3 681 75
84 java.math 9 9
85 java.net 23 3 278 1 1 21 3 274 4
86 java.nio 44 47 361 5 38 41 1 259 102
87 java.rmi 71 71
88 java.security 21 543 11 10 539 4
89 java.sql 15 1 303 1 1 4 9 1 303
90 java.text 134 134
91 java.time 123 35 88
92 java.util 47 48 2 1218 1 34 2 3 5 2 1 2 2 704 514
93 javafx.scene.web 1 1
94 javax.accessibility 31 31
95 javax.activation 2 7 1 1 7
138 kotlin 16 1849 14 2 1836 13
139 liquibase.database.jvm 1 1
140 liquibase.statement.core 1 1
141 net.lingala.zip4j 2 2
142 net.schmizz.sshj 4 2 2
143 net.sf.json 2 338 2 321 17
144 net.sf.saxon.s9api 5 5
232 org.springframework.boot.jdbc 1 1
233 org.springframework.cache 13 13
234 org.springframework.context 3 3
235 org.springframework.core.io 3 17 5 2 16 1 5
236 org.springframework.data.repository 1 1
237 org.springframework.http 14 77 14 67 10
238 org.springframework.jdbc.core 19 19
243 org.springframework.security.core.userdetails 2 1 1
244 org.springframework.security.web.savedrequest 6 6
245 org.springframework.ui 32 32
246 org.springframework.util 3 10 142 3 10 90 52
247 org.springframework.validation 13 13
248 org.springframework.web.client 13 3 13 3
249 org.springframework.web.context.request 8 8
269 ratpack.http 10 10 10 10
270 ratpack.util 35 35
271 retrofit2 1 1 1 1
272 software.amazon.awssdk.transfer.s3.model 8 8
273 sun.awt 5 5
274 sun.jvmstat.perfdata.monitor.protocol.local 3 3
275 sun.jvmstat.perfdata.monitor.protocol.rmi 1 1

View File

@@ -18,10 +18,10 @@ Java framework & library support
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,730,43,9,,,,,
JBoss Logging,``org.jboss.logging``,,,324,,,,,,
`JSON-java <https://github.com/stleary/JSON-java>`_,``org.json``,,236,,,,,,,
Java Standard Library,``java.*``,10,4264,255,95,,9,,,26
Java Standard Library,``java.*``,10,4264,259,99,,9,,,26
Java extensions,"``javax.*``, ``jakarta.*``",69,3257,90,10,4,2,1,1,4
Kotlin Standard Library,``kotlin*``,,1849,16,14,,,,,2
`Spring <https://spring.io/>`_,``org.springframework.*``,38,486,122,5,,28,14,,35
Others,"``actions.osgi``, ``antlr``, ``ch.ethz.ssh2``, ``cn.hutool.core.codec``, ``com.alibaba.druid.sql``, ``com.alibaba.fastjson2``, ``com.amazonaws.auth``, ``com.auth0.jwt.algorithms``, ``com.azure.identity``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.google.gson``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.microsoft.sqlserver.jdbc``, ``com.mitchellbosecke.pebble``, ``com.mongodb``, ``com.opensymphony.xwork2``, ``com.rabbitmq.client``, ``com.sshtools.j2ssh.authentication``, ``com.sun.crypto.provider``, ``com.sun.jndi.ldap``, ``com.sun.net.httpserver``, ``com.sun.net.ssl``, ``com.sun.rowset``, ``com.sun.security.auth.module``, ``com.sun.security.ntlm``, ``com.sun.security.sasl.digest``, ``com.thoughtworks.xstream``, ``com.trilead.ssh2``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.text``, ``groovy.util``, ``hudson``, ``io.jsonwebtoken``, ``io.netty.bootstrap``, ``io.netty.buffer``, ``io.netty.channel``, ``io.netty.handler.codec``, ``io.netty.handler.ssl``, ``io.netty.handler.stream``, ``io.netty.resolver``, ``io.netty.util``, ``io.undertow.server.handlers.resource``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``liquibase.database.jvm``, ``liquibase.statement.core``, ``net.schmizz.sshj``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.cxf.catalog``, ``org.apache.cxf.common.classloader``, ``org.apache.cxf.common.jaxb``, ``org.apache.cxf.common.logging``, ``org.apache.cxf.configuration.jsse``, ``org.apache.cxf.helpers``, ``org.apache.cxf.resource``, ``org.apache.cxf.staxutils``, ``org.apache.cxf.tools.corba.utils``, ``org.apache.cxf.tools.util``, ``org.apache.cxf.transform``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hadoop.hive.ql.exec``, ``org.apache.hadoop.hive.ql.metadata``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.ibatis.mapping``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.shiro.mgt``, ``org.apache.sshd.client.session``, ``org.apache.struts.beanvalidation.validation.interceptor``, ``org.apache.struts2``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.cargo.container.installer``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.eclipse.jetty.client``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.hibernate``, ``org.influxdb``, ``org.jboss.vfs``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.jooq``, ``org.keycloak.models.map.storage``, ``org.kohsuke.stapler``, ``org.lastaflute.web``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.owasp.esapi``, ``org.pac4j.jwt.config.encryption``, ``org.pac4j.jwt.config.signature``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.w3c.dom``, ``org.xml.sax``, ``org.xmlpull.v1``, ``org.yaml.snakeyaml``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``, ``sun.awt``, ``sun.jvmstat.perfdata.monitor.protocol.local``, ``sun.jvmstat.perfdata.monitor.protocol.rmi``, ``sun.management.spi``, ``sun.misc``, ``sun.net.ftp``, ``sun.net.www.protocol.http``, ``sun.nio.ch``, ``sun.security.acl``, ``sun.security.jgss.krb5``, ``sun.security.krb5``, ``sun.security.pkcs``, ``sun.security.pkcs11``, ``sun.security.provider``, ``sun.security.ssl``, ``sun.security.x509``, ``sun.tools.jconsole``, ``sun.util.logging.internal``",132,10603,898,130,6,22,18,,208
Totals,,311,25147,2600,369,16,128,33,1,409
`Spring <https://spring.io/>`_,``org.springframework.*``,38,486,143,26,,28,14,,35
Others,"``actions.osgi``, ``antlr``, ``ch.ethz.ssh2``, ``cn.hutool.core.codec``, ``com.alibaba.druid.sql``, ``com.alibaba.fastjson2``, ``com.amazonaws.auth``, ``com.auth0.jwt.algorithms``, ``com.azure.identity``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.google.gson``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.microsoft.sqlserver.jdbc``, ``com.mitchellbosecke.pebble``, ``com.mongodb``, ``com.opensymphony.xwork2``, ``com.rabbitmq.client``, ``com.sshtools.j2ssh.authentication``, ``com.sun.crypto.provider``, ``com.sun.jndi.ldap``, ``com.sun.net.httpserver``, ``com.sun.net.ssl``, ``com.sun.rowset``, ``com.sun.security.auth.module``, ``com.sun.security.ntlm``, ``com.sun.security.sasl.digest``, ``com.thoughtworks.xstream``, ``com.trilead.ssh2``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.text``, ``groovy.util``, ``hudson``, ``io.jsonwebtoken``, ``io.netty.bootstrap``, ``io.netty.buffer``, ``io.netty.channel``, ``io.netty.handler.codec``, ``io.netty.handler.ssl``, ``io.netty.handler.stream``, ``io.netty.resolver``, ``io.netty.util``, ``io.undertow.server.handlers.resource``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``liquibase.database.jvm``, ``liquibase.statement.core``, ``net.lingala.zip4j``, ``net.schmizz.sshj``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.cxf.catalog``, ``org.apache.cxf.common.classloader``, ``org.apache.cxf.common.jaxb``, ``org.apache.cxf.common.logging``, ``org.apache.cxf.configuration.jsse``, ``org.apache.cxf.helpers``, ``org.apache.cxf.resource``, ``org.apache.cxf.staxutils``, ``org.apache.cxf.tools.corba.utils``, ``org.apache.cxf.tools.util``, ``org.apache.cxf.transform``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hadoop.hive.ql.exec``, ``org.apache.hadoop.hive.ql.metadata``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.ibatis.mapping``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.shiro.mgt``, ``org.apache.sshd.client.session``, ``org.apache.struts.beanvalidation.validation.interceptor``, ``org.apache.struts2``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.cargo.container.installer``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.eclipse.jetty.client``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.hibernate``, ``org.influxdb``, ``org.jboss.vfs``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.jooq``, ``org.keycloak.models.map.storage``, ``org.kohsuke.stapler``, ``org.lastaflute.web``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.owasp.esapi``, ``org.pac4j.jwt.config.encryption``, ``org.pac4j.jwt.config.signature``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.w3c.dom``, ``org.xml.sax``, ``org.xmlpull.v1``, ``org.yaml.snakeyaml``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``, ``software.amazon.awssdk.transfer.s3.model``, ``sun.awt``, ``sun.jvmstat.perfdata.monitor.protocol.local``, ``sun.jvmstat.perfdata.monitor.protocol.rmi``, ``sun.management.spi``, ``sun.misc``, ``sun.net.ftp``, ``sun.net.www.protocol.http``, ``sun.nio.ch``, ``sun.security.acl``, ``sun.security.jgss.krb5``, ``sun.security.krb5``, ``sun.security.pkcs``, ``sun.security.pkcs11``, ``sun.security.provider``, ``sun.security.ssl``, ``sun.security.x509``, ``sun.tools.jconsole``, ``sun.util.logging.internal``",132,10603,908,140,6,22,18,,208
Totals,,311,25147,2635,404,16,128,33,1,409

View File

@@ -36,30 +36,32 @@ int lowerBound(VarAccess va) {
)
}
/** Gets an access to `e`, which is either a variable or a method. */
pragma[nomagic]
private Expr getAnAccess(Element e) {
result = e.(Variable).getAnAccess()
or
result.(MethodCall).getMethod() = e
}
pragma[nomagic]
private predicate lengthAccess(FieldAccess fa, Element qualifier) {
fa.getQualifier() = getAnAccess(qualifier) and
fa.getField().hasName("length")
}
/**
* Holds if the index expression is a `VarAccess`, where the variable has been confirmed to be less
* than the length.
*/
predicate lessthanLength(ArrayAccess a) {
exists(ComparisonExpr lessThanLength, VarAccess va |
exists(ComparisonExpr lessThanLength, VarAccess va, Element qualifier |
va = a.getIndexExpr() and
conditionHolds(lessThanLength, va)
|
lessThanLength.getGreaterOperand().(FieldAccess).getQualifier() = arrayReference(a) and
lessThanLength.getGreaterOperand().(FieldAccess).getField().hasName("length") and
lengthAccess(lessThanLength.getGreaterOperand(), qualifier) and
a.getArray() = getAnAccess(qualifier) and
lessThanLength.getLesserOperand() = va.getVariable().getAnAccess() and
lessThanLength.isStrict()
)
}
/**
* Return all other references to the array accessed in the `ArrayAccess`.
*/
pragma[nomagic]
private Expr arrayReference(ArrayAccess arrayAccess) {
// Array is stored in a variable.
result = arrayAccess.getArray().(VarAccess).getVariable().getAnAccess()
or
// Array is returned from a method.
result.(MethodCall).getMethod() = arrayAccess.getArray().(MethodCall).getMethod()
}

View File

@@ -1203,6 +1203,77 @@ module Http {
* Gets the argument, if any, specifying the cookie value.
*/
DataFlow::Node getValueArg() { result = super.getValueArg() }
/**
* Holds if the `Secure` flag of the cookie is known to have a value of `b`.
*/
predicate hasSecureFlag(boolean b) { super.hasSecureFlag(b) }
/**
* Holds if the `HttpOnly` flag of the cookie is known to have a value of `b`.
*/
predicate hasHttpOnlyFlag(boolean b) { super.hasHttpOnlyFlag(b) }
/**
* Holds if the `SameSite` attribute of the cookie is known to have a value of `v`.
*/
predicate hasSameSiteAttribute(CookieWrite::SameSiteValue v) { super.hasSameSiteAttribute(v) }
}
/**
* A dataflow call node to a method that sets a cookie in an http response,
* and has common keyword arguments `secure`, `httponly`, and `samesite` to set the attributes of the cookie.
*
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
*/
abstract class SetCookieCall extends CookieWrite::Range, DataFlow::CallCfgNode {
override predicate hasSecureFlag(boolean b) {
super.hasSecureFlag(b)
or
exists(DataFlow::Node arg, BooleanLiteral bool | arg = this.getArgByName("secure") |
DataFlow::localFlow(DataFlow::exprNode(bool), arg) and
b = bool.booleanValue()
)
or
not exists(this.getArgByName("secure")) and
not exists(this.getKwargs()) and
b = false
}
override predicate hasHttpOnlyFlag(boolean b) {
super.hasHttpOnlyFlag(b)
or
exists(DataFlow::Node arg, BooleanLiteral bool | arg = this.getArgByName("httponly") |
DataFlow::localFlow(DataFlow::exprNode(bool), arg) and
b = bool.booleanValue()
)
or
not exists(this.getArgByName("httponly")) and
not exists(this.getKwargs()) and
b = false
}
override predicate hasSameSiteAttribute(CookieWrite::SameSiteValue v) {
super.hasSameSiteAttribute(v)
or
exists(DataFlow::Node arg, StringLiteral str | arg = this.getArgByName("samesite") |
DataFlow::localFlow(DataFlow::exprNode(str), arg) and
(
str.getText().toLowerCase() = "strict" and
v instanceof CookieWrite::SameSiteStrict
or
str.getText().toLowerCase() = "lax" and
v instanceof CookieWrite::SameSiteLax
or
str.getText().toLowerCase() = "none" and
v instanceof CookieWrite::SameSiteNone
)
)
or
not exists(this.getArgByName("samesite")) and
not exists(this.getKwargs()) and
v instanceof CookieWrite::SameSiteLax // Lax is the default
}
}
/** Provides a class for modeling new cookie writes on HTTP responses. */
@@ -1231,6 +1302,91 @@ module Http {
* Gets the argument, if any, specifying the cookie value.
*/
abstract DataFlow::Node getValueArg();
/**
* Holds if the `Secure` flag of the cookie is known to have a value of `b`.
*/
predicate hasSecureFlag(boolean b) {
exists(StringLiteral sl |
// `sl` is likely a substring of the header
TaintTracking::localTaint(DataFlow::exprNode(sl), this.getHeaderArg()) and
sl.getText().regexpMatch("(?i).*;\\s*secure(;.*|\\s*)") and
b = true
or
// `sl` is the entire header
DataFlow::localFlow(DataFlow::exprNode(sl), this.getHeaderArg()) and
not sl.getText().regexpMatch("(?i).*;\\s*secure(;.*|\\s*)") and
b = false
)
}
/**
* Holds if the `HttpOnly` flag of the cookie is known to have a value of `b`.
*/
predicate hasHttpOnlyFlag(boolean b) {
exists(StringLiteral sl |
// `sl` is likely a substring of the header
TaintTracking::localTaint(DataFlow::exprNode(sl), this.getHeaderArg()) and
sl.getText().regexpMatch("(?i).*;\\s*httponly(;.*|\\s*)") and
b = true
or
// `sl` is the entire header
DataFlow::localFlow(DataFlow::exprNode(sl), this.getHeaderArg()) and
not sl.getText().regexpMatch("(?i).*;\\s*httponly(;.*|\\s*)") and
b = false
)
}
/**
* Holds if the `SameSite` flag of the cookie is known to have a value of `v`.
*/
predicate hasSameSiteAttribute(SameSiteValue v) {
exists(StringLiteral sl |
// `sl` is likely a substring of the header
TaintTracking::localTaint(DataFlow::exprNode(sl), this.getHeaderArg()) and
(
sl.getText().regexpMatch("(?i).*;\\s*samesite=strict(;.*|\\s*)") and
v instanceof SameSiteStrict
or
sl.getText().regexpMatch("(?i).*;\\s*samesite=lax(;.*|\\s*)") and
v instanceof SameSiteLax
or
sl.getText().regexpMatch("(?i).*;\\s*samesite=none(;.*|\\s*)") and
v instanceof SameSiteNone
)
or
// `sl` is the entire header
DataFlow::localFlow(DataFlow::exprNode(sl), this.getHeaderArg()) and
not sl.getText().regexpMatch("(?i).*;\\s*samesite=(strict|lax|none)(;.*|\\s*)") and
v instanceof SameSiteLax // Lax is the default
)
}
}
private newtype TSameSiteValue =
TSameSiteStrict() or
TSameSiteLax() or
TSameSiteNone()
/** A possible value for the SameSite attribute of a cookie. */
class SameSiteValue extends TSameSiteValue {
/** Gets a string representation of this value. */
string toString() { none() }
}
/** A `Strict` value of the `SameSite` attribute. */
class SameSiteStrict extends SameSiteValue, TSameSiteStrict {
override string toString() { result = "Strict" }
}
/** A `Lax` value of the `SameSite` attribute. */
class SameSiteLax extends SameSiteValue, TSameSiteLax {
override string toString() { result = "Lax" }
}
/** A `None` value of the `SameSite` attribute. */
class SameSiteNone extends SameSiteValue, TSameSiteNone {
override string toString() { result = "None" }
}
}

View File

@@ -219,6 +219,12 @@ class CallCfgNode extends CfgNode, LocalSourceNode {
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
Node getArgByName(string name) { result.asCfgNode() = node.getArgByName(name) }
/** Gets the data-flow node corresponding to the first tuple (*) argument of the call corresponding to this data-flow node, if any. */
Node getStarArg() { result.asCfgNode() = node.getStarArg() }
/** Gets the data-flow node corresponding to a dictionary (**) argument of the call corresponding to this data-flow node, if any. */
Node getKwargs() { result.asCfgNode() = node.getKwargs() }
}
/**

View File

@@ -653,8 +653,7 @@ module AiohttpWebModel {
/**
* A call to `set_cookie` on a HTTP Response.
*/
class AiohttpResponseSetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::CallCfgNode
{
class AiohttpResponseSetCookieCall extends Http::Server::SetCookieCall {
AiohttpResponseSetCookieCall() {
this = aiohttpResponseInstance().getMember("set_cookie").getACall()
}

View File

@@ -2170,7 +2170,7 @@ module PrivateDjango {
/**
* A call to `set_cookie` on a HTTP Response.
*/
class DjangoResponseSetCookieCall extends Http::Server::CookieWrite::Range,
class DjangoResponseSetCookieCall extends Http::Server::SetCookieCall,
DataFlow::MethodCallNode
{
DjangoResponseSetCookieCall() {

View File

@@ -348,7 +348,7 @@ module FastApi {
/**
* A call to `set_cookie` on a FastAPI Response.
*/
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
private class SetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
SetCookieCall() { this.calls(instance(), "set_cookie") }
override DataFlow::Node getHeaderArg() { none() }

View File

@@ -583,9 +583,7 @@ module Flask {
*
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.set_cookie
*/
class FlaskResponseSetCookieCall extends Http::Server::CookieWrite::Range,
DataFlow::MethodCallNode
{
class FlaskResponseSetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
FlaskResponseSetCookieCall() { this.calls(Flask::Response::instance(), "set_cookie") }
override DataFlow::Node getHeaderArg() { none() }

View File

@@ -255,7 +255,7 @@ module Pyramid {
}
/** A call to `response.set_cookie`. */
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
private class SetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
SetCookieCall() { this.calls(instance(), "set_cookie") }
override DataFlow::Node getHeaderArg() { none() }

View File

@@ -592,7 +592,7 @@ module Tornado {
*
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_cookie
*/
class TornadoRequestHandlerSetCookieCall extends Http::Server::CookieWrite::Range,
class TornadoRequestHandlerSetCookieCall extends Http::Server::SetCookieCall,
DataFlow::MethodCallNode
{
TornadoRequestHandlerSetCookieCall() {

View File

@@ -235,9 +235,7 @@ private module Twisted {
*
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#addCookie
*/
class TwistedRequestAddCookieCall extends Http::Server::CookieWrite::Range,
DataFlow::MethodCallNode
{
class TwistedRequestAddCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
TwistedRequestAddCookieCall() { this.calls(Twisted::Request::instance(), "addCookie") }
override DataFlow::Node getHeaderArg() { none() }

View File

@@ -4,10 +4,9 @@
<qhelp>
<overview>
<p>Setting the 'secure' flag on a cookie to <code>False</code> can cause it to be sent in cleartext.
Setting the 'httponly' flag on a cookie to <code>False</code> may allow attackers access it via JavaScript.
Setting the 'samesite' flag on a cookie to <code>'None'</code> will make the cookie to be sent in third-party
contexts which may be attacker-controlled.</p>
<p>Cookies without the <code>Secure</code> flag set may be transmittd using HTTP instead of HTTPS, which leaves it vulnerable to being read by a third party.</p>
<p>Cookies without the <code>HttpOnly</code> flag set are accessible to JavaScript running in the same origin. In case of a Cross-Site Scripting (XSS) vulnerability, the cookie can be stolen by a malicious script.</p>
<p>Cookies with the <code>SameSite</code> attribute set to <code>'None'</code> will be sent with cross-origin requests, which can be controlled by third-party JavaScript code and allow for Cross-Site Request Forgery (CSRF) attacks.</p>
</overview>
<recommendation>
@@ -18,9 +17,8 @@ contexts which may be attacker-controlled.</p>
</recommendation>
<example>
<p>This example shows two ways of adding a cookie to a Flask response. The first way uses <code>set_cookie</code>'s
secure flag and the second adds the secure flag in the cookie's raw value.</p>
<sample src="InsecureCookie.py" />
<p>In the following examples, the cases marked GOOD show secure cookie attributes being set; whereas in the cases marked BAD they are not set.</p>
<sample src="examples/InsecureCookie.py" />
</example>
<references>

View File

@@ -0,0 +1,51 @@
/**
* @name Failure to use secure cookies
* @description Insecure cookies may be sent in cleartext, which makes them vulnerable to
* interception.
* @kind problem
* @problem.severity warning
* @security-severity 5.0
* @precision high
* @id py/insecure-cookie
* @tags security
* external/cwe/cwe-614
* external/cwe/cwe-1004
* external/cwe/cwe-1275
*/
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.Concepts
predicate hasProblem(Http::Server::CookieWrite cookie, string alert, int idx) {
cookie.hasSecureFlag(false) and
alert = "Secure" and
idx = 0
or
cookie.hasHttpOnlyFlag(false) and
alert = "HttpOnly" and
idx = 1
or
cookie.hasSameSiteAttribute(any(Http::Server::CookieWrite::SameSiteNone v)) and
alert = "SameSite" and
idx = 2
}
predicate hasAlert(Http::Server::CookieWrite cookie, string alert) {
exists(int numProblems | numProblems = strictcount(string p | hasProblem(cookie, p, _)) |
numProblems = 1 and
alert = any(string prob | hasProblem(cookie, prob, _)) + " attribute"
or
numProblems = 2 and
alert =
strictconcat(string prob, int idx | hasProblem(cookie, prob, idx) | prob, " and " order by idx)
+ " attributes"
or
numProblems = 3 and
alert = "Secure, HttpOnly, and SameSite attributes"
)
}
from Http::Server::CookieWrite cookie, string alert
where hasAlert(cookie, alert)
select cookie, "Cookie is added without the " + alert + " properly set."

View File

@@ -0,0 +1,20 @@
from flask import Flask, request, make_response, Response
@app.route("/good1")
def good1():
resp = make_response()
resp.set_cookie("name", value="value", secure=True, httponly=True, samesite='Strict') # GOOD: Attributes are securely set
return resp
@app.route("/good2")
def good2():
resp = make_response()
resp.headers['Set-Cookie'] = "name=value; Secure; HttpOnly; SameSite=Strict" # GOOD: Attributes are securely set
return resp
@app.route("/bad1")
resp = make_response()
resp.set_cookie("name", value="value", samesite='None') # BAD: the SameSite attribute is set to 'None' and the 'Secure' and 'HttpOnly' attributes are set to False by default.
return resp

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* The `py/cookie-injection` query, originally contributed to the experimental query pack by @jorgectf, has been promoted to the main query pack. This query finds instances of cookies being set without the `Secure`, `HttpOnly`, or `SameSite` attributes set to secure values.

View File

@@ -1,15 +0,0 @@
from flask import Flask, request, make_response, Response
@app.route("/1")
def true():
resp = make_response()
resp.set_cookie("name", value="value", secure=True)
return resp
@app.route("/2")
def flask_make_response():
resp = make_response("hello")
resp.headers['Set-Cookie'] = "name=value; Secure;"
return resp

View File

@@ -1,31 +0,0 @@
/**
* @name Failure to use secure cookies
* @description Insecure cookies may be sent in cleartext, which makes them vulnerable to
* interception.
* @kind problem
* @problem.severity error
* @security-severity 5.0
* @precision ???
* @id py/insecure-cookie
* @tags security
* experimental
* external/cwe/cwe-614
*/
// TODO: determine precision above
import python
import semmle.python.dataflow.new.DataFlow
import experimental.semmle.python.Concepts
import experimental.semmle.python.CookieHeader
from Cookie cookie, string alert
where
not cookie.isSecure() and
alert = "secure"
or
not cookie.isHttpOnly() and
alert = "httponly"
or
not cookie.isSameSite() and
alert = "samesite"
select cookie, "Cookie is added without the '" + alert + "' flag properly set."

View File

@@ -278,55 +278,6 @@ class CsvWriter extends DataFlow::Node instanceof CsvWriter::Range {
DataFlow::Node getAnInput() { result = super.getAnInput() }
}
/**
* A data-flow node that sets a cookie in an HTTP response.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `Cookie::Range` instead.
*/
class Cookie extends Http::Server::CookieWrite instanceof Cookie::Range {
/**
* Holds if this cookie is secure.
*/
predicate isSecure() { super.isSecure() }
/**
* Holds if this cookie is HttpOnly.
*/
predicate isHttpOnly() { super.isHttpOnly() }
/**
* Holds if the cookie is SameSite
*/
predicate isSameSite() { super.isSameSite() }
}
/** Provides a class for modeling new cookie writes on HTTP responses. */
module Cookie {
/**
* A data-flow node that sets a cookie in an HTTP response.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `Cookie` instead.
*/
abstract class Range extends Http::Server::CookieWrite::Range {
/**
* Holds if this cookie is secure.
*/
abstract predicate isSecure();
/**
* Holds if this cookie is HttpOnly.
*/
abstract predicate isHttpOnly();
/**
* Holds if the cookie is SameSite.
*/
abstract predicate isSameSite();
}
}
/** Provides classes for modeling JWT encoding-related APIs. */
module JwtEncoding {
/**

View File

@@ -1,76 +0,0 @@
/**
* Temporary: provides a class to extend current cookies to header declarations
*/
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import experimental.semmle.python.Concepts
import semmle.python.Concepts
/**
* Gets a header setting a cookie.
*
* Given the following example:
*
* ```py
* @app.route("/")
* def flask_make_response():
* resp = make_response("")
* resp.headers['Set-Cookie'] = "name=value; Secure;"
* return resp
* ```
*
* * `this` would be `resp.headers['Set-Cookie'] = "name=value; Secure;"`.
* * `isSecure()` predicate would succeed.
* * `isHttpOnly()` predicate would fail.
* * `isSameSite()` predicate would fail.
* * `getName()` and `getValue()` results would be `"name=value; Secure;"`.
*/
class CookieHeader extends Cookie::Range instanceof Http::Server::ResponseHeaderWrite {
CookieHeader() {
exists(StringLiteral str |
str.getText() = "Set-Cookie" and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getNameArg())
)
}
override predicate isSecure() {
exists(StringLiteral str |
str.getText().regexpMatch(".*; *Secure;.*") and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getValueArg())
)
}
override predicate isHttpOnly() {
exists(StringLiteral str |
str.getText().regexpMatch(".*; *HttpOnly;.*") and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getValueArg())
)
}
override predicate isSameSite() {
exists(StringLiteral str |
str.getText().regexpMatch(".*; *SameSite=(Strict|Lax);.*") and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getValueArg())
)
}
override DataFlow::Node getNameArg() {
result = this.(Http::Server::ResponseHeaderWrite).getValueArg()
}
override DataFlow::Node getValueArg() {
result = this.(Http::Server::ResponseHeaderWrite).getValueArg()
}
override DataFlow::Node getHeaderArg() { none() }
}

View File

@@ -4,7 +4,6 @@
private import experimental.semmle.python.frameworks.AsyncSsh
private import experimental.semmle.python.frameworks.Stdlib
private import experimental.semmle.python.frameworks.Flask
private import experimental.semmle.python.frameworks.Django
private import experimental.semmle.python.frameworks.LDAP
private import experimental.semmle.python.frameworks.Netmiko

View File

@@ -87,63 +87,6 @@ private module ExperimentalPrivateDjango {
or
result = baseClassRef().getReturn().getAMember()
}
/**
* Gets a call to `set_cookie()`.
*
* Given the following example:
*
* ```py
* def django_response(request):
* resp = django.http.HttpResponse()
* resp.set_cookie("name", "value", secure=True, httponly=True, samesite='Lax')
* return resp
* ```
*
* * `this` would be `resp.set_cookie("name", "value", secure=False, httponly=False, samesite='None')`.
* * `getName()`'s result would be `"name"`.
* * `getValue()`'s result would be `"value"`.
* * `isSecure()` predicate would succeed.
* * `isHttpOnly()` predicate would succeed.
* * `isSameSite()` predicate would succeed.
*/
class DjangoResponseSetCookieCall extends DataFlow::MethodCallNode, Cookie::Range {
DjangoResponseSetCookieCall() {
this.calls(PrivateDjango::DjangoImpl::DjangoHttp::Response::HttpResponse::instance(),
"set_cookie")
}
override DataFlow::Node getNameArg() {
result in [this.getArg(0), this.getArgByName("key")]
}
override DataFlow::Node getValueArg() {
result in [this.getArg(1), this.getArgByName("value")]
}
override predicate isSecure() {
DataFlow::exprNode(any(True t))
.(DataFlow::LocalSourceNode)
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("secure"))
}
override predicate isHttpOnly() {
DataFlow::exprNode(any(True t))
.(DataFlow::LocalSourceNode)
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("httponly"))
}
override predicate isSameSite() {
exists(StringLiteral str |
str.getText() in ["Strict", "Lax"] and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("samesite"))
)
}
override DataFlow::Node getHeaderArg() { none() }
}
}
}
}

View File

@@ -1,62 +0,0 @@
/**
* Provides classes modeling security-relevant aspects of the `flask` PyPI package.
* See https://flask.palletsprojects.com/en/1.1.x/.
*/
private import python
private import semmle.python.frameworks.Flask
private import semmle.python.dataflow.new.DataFlow
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.Flask
module ExperimentalFlask {
/**
* Gets a call to `set_cookie()`.
*
* Given the following example:
*
* ```py
* @app.route("/")
* def false():
* resp = make_response()
* resp.set_cookie("name", value="value", secure=True, httponly=True, samesite='Lax')
* return resp
* ```
*
* * `this` would be `resp.set_cookie("name", value="value", secure=False, httponly=False, samesite='None')`.
* * `getName()`'s result would be `"name"`.
* * `getValue()`'s result would be `"value"`.
* * `isSecure()` predicate would succeed.
* * `isHttpOnly()` predicate would succeed.
* * `isSameSite()` predicate would succeed.
*/
class FlaskSetCookieCall extends Cookie::Range instanceof Flask::FlaskResponseSetCookieCall {
override DataFlow::Node getNameArg() { result = this.getNameArg() }
override DataFlow::Node getValueArg() { result = this.getValueArg() }
override predicate isSecure() {
DataFlow::exprNode(any(True t))
.(DataFlow::LocalSourceNode)
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("secure"))
}
override predicate isHttpOnly() {
DataFlow::exprNode(any(True t))
.(DataFlow::LocalSourceNode)
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("httponly"))
}
override predicate isSameSite() {
exists(StringLiteral str |
str.getText() in ["Strict", "Lax"] and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("samesite"))
)
}
override DataFlow::Node getHeaderArg() { none() }
}
}

View File

@@ -405,7 +405,10 @@ module HttpServerHttpRedirectResponseTest implements TestSig {
module HttpServerCookieWriteTest implements TestSig {
string getARelevantTag() {
result in ["CookieWrite", "CookieRawHeader", "CookieName", "CookieValue"]
result in [
"CookieWrite", "CookieRawHeader", "CookieName", "CookieValue", "CookieSecure",
"CookieHttpOnly", "CookieSameSite"
]
}
predicate hasActualResult(Location location, string element, string tag, string value) {
@@ -428,6 +431,20 @@ module HttpServerCookieWriteTest implements TestSig {
element = cookieWrite.toString() and
value = prettyNodeForInlineTest(cookieWrite.getValueArg()) and
tag = "CookieValue"
or
element = cookieWrite.toString() and
value = any(boolean b | cookieWrite.hasSecureFlag(b)).toString() and
tag = "CookieSecure"
or
element = cookieWrite.toString() and
value = any(boolean b | cookieWrite.hasHttpOnlyFlag(b)).toString() and
tag = "CookieHttpOnly"
or
element = cookieWrite.toString() and
value =
any(Http::Server::CookieWrite::SameSiteValue v | cookieWrite.hasSameSiteAttribute(v))
.toString() and
tag = "CookieSameSite"
)
)
}

View File

@@ -1,30 +0,0 @@
| django_bad.py:6:5:7:52 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:6:5:7:52 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:6:5:7:52 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |
| django_bad.py:13:5:13:26 | ControlFlowNode for Subscript | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:13:5:13:26 | ControlFlowNode for Subscript | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:13:5:13:26 | ControlFlowNode for Subscript | Cookie is added without the 'secure' flag properly set. |
| django_bad.py:19:5:21:66 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:19:5:21:66 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:19:5:21:66 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |
| django_bad.py:27:5:27:26 | ControlFlowNode for Subscript | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:27:5:27:26 | ControlFlowNode for Subscript | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:27:5:27:26 | ControlFlowNode for Subscript | Cookie is added without the 'secure' flag properly set. |
| django_good.py:19:5:19:44 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| django_good.py:19:5:19:44 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| django_good.py:19:5:19:44 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |
| flask_bad.py:9:5:10:52 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| flask_bad.py:9:5:10:52 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| flask_bad.py:9:5:10:52 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |
| flask_bad.py:17:5:17:30 | ControlFlowNode for Subscript | Cookie is added without the 'httponly' flag properly set. |
| flask_bad.py:17:5:17:30 | ControlFlowNode for Subscript | Cookie is added without the 'samesite' flag properly set. |
| flask_bad.py:17:5:17:30 | ControlFlowNode for Subscript | Cookie is added without the 'secure' flag properly set. |
| flask_bad.py:24:5:25:52 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| flask_bad.py:24:5:25:52 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| flask_bad.py:24:5:25:52 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |
| flask_bad.py:32:5:32:30 | ControlFlowNode for Subscript | Cookie is added without the 'httponly' flag properly set. |
| flask_bad.py:32:5:32:30 | ControlFlowNode for Subscript | Cookie is added without the 'samesite' flag properly set. |
| flask_bad.py:32:5:32:30 | ControlFlowNode for Subscript | Cookie is added without the 'secure' flag properly set. |
| flask_good.py:23:5:23:57 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| flask_good.py:23:5:23:57 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| flask_good.py:23:5:23:57 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |

View File

@@ -1 +0,0 @@
experimental/Security/CWE-614/InsecureCookie.ql

View File

@@ -1,28 +0,0 @@
import django.http
def django_response(request):
resp = django.http.HttpResponse()
resp.set_cookie("name", "value", secure=False,
httponly=False, samesite='None')
return resp
def django_response():
response = django.http.HttpResponse()
response['Set-Cookie'] = "name=value; SameSite=None;"
return response
def django_response(request):
resp = django.http.HttpResponse()
resp.set_cookie(django.http.request.GET.get("name"),
django.http.request.GET.get("value"),
secure=False, httponly=False, samesite='None')
return resp
def django_response():
response = django.http.HttpResponse()
response['Set-Cookie'] = f"{django.http.request.GET.get('name')}={django.http.request.GET.get('value')}; SameSite=None;"
return response

View File

@@ -1,20 +0,0 @@
import django.http
def django_response(request):
resp = django.http.HttpResponse()
resp['Set-Cookie'] = "name=value; Secure; HttpOnly; SameSite=Lax;"
return resp
def django_response(request):
resp = django.http.HttpResponse()
resp.set_cookie("name", "value", secure=True,
httponly=True, samesite='Lax')
return resp
def indeterminate(secure):
resp = django.http.HttpResponse()
resp.set_cookie("name", "value", secure)
return resp

View File

@@ -1,37 +0,0 @@
from flask import Flask, request, make_response, Response
app = Flask(__name__)
@app.route("/1")
def false():
resp = make_response()
resp.set_cookie("name", value="value", secure=False,
httponly=False, samesite='None')
return resp
@app.route("/2")
def flask_Response():
resp = Response()
resp.headers['Set-Cookie'] = "name=value; SameSite=None;"
return resp
@app.route("/3")
def false():
resp = make_response()
resp.set_cookie(request.args["name"], value=request.args["value"], secure=False,
httponly=False, samesite='None')
return resp
@app.route("/4")
def flask_Response():
resp = Response()
resp.headers['Set-Cookie'] = f"{request.args['name']}={request.args['value']}; SameSite=None;"
return resp
# if __name__ == "__main__":
# app.run(debug=True)

View File

@@ -1,28 +0,0 @@
from flask import Flask, request, make_response, Response
app = Flask(__name__)
@app.route("/1")
def true():
resp = make_response()
resp.set_cookie("name", value="value", secure=True,
httponly=True, samesite='Lax')
return resp
@app.route("/2")
def flask_Response():
resp = Response()
resp.headers['Set-Cookie'] = "name=value; Secure; HttpOnly; SameSite=Lax;"
return resp
def indeterminate(secure):
resp = make_response()
resp.set_cookie("name", value="value", secure=secure)
return resp
# if __name__ == "__main__":
# app.run(debug=True)

View File

@@ -96,10 +96,12 @@ async def streaming_response(request): # $ requestHandler
async def setting_cookie(request): # $ requestHandler
resp = web.Response(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
resp.cookies["key"] = "value" # $ CookieWrite CookieName="key" CookieValue="value"
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
resp.set_cookie("key3", "value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
resp.set_cookie(name="key3", value="value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie("key3", "value3") # $ CookieWrite CookieName="key3" CookieValue="value3" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(name="key3", value="value3") # $ CookieWrite CookieName="key3" CookieValue="value3" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.del_cookie("key4") # $ CookieWrite CookieName="key4"
resp.set_cookie(name="key5", value="value5", secure=True, httponly=True, samesite="Strict") # $ CookieWrite CookieName="key5" CookieValue="value5" CookieSecure=true CookieHttpOnly=true CookieSameSite=Strict
resp.headers["Set-Cookie"] = "key6=value6; Secure; HttpOnly; SameSite=Strict" # $ headerWriteName="Set-Cookie" headerWriteValue="key6=value6; Secure; HttpOnly; SameSite=Strict" CookieWrite CookieRawHeader="key6=value6; Secure; HttpOnly; SameSite=Strict" CookieSecure=true CookieHttpOnly=true CookieSameSite=Strict
return resp

View File

@@ -128,11 +128,14 @@ def safe__custom_json_response(request):
def setting_cookie(request):
resp = HttpResponse() # $ HttpResponse mimetype=text/html
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.set_cookie(key="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(key="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.cookies["key3"] = "value3" # $ CookieWrite CookieName="key3" CookieValue="value3"
resp.delete_cookie("key4") # $ CookieWrite CookieName="key4"
resp.delete_cookie(key="key4") # $ CookieWrite CookieName="key4"
resp["Set-Cookie"] = "key5=value5" # $ headerWriteName="Set-Cookie" headerWriteValue="key5=value5" CookieWrite CookieRawHeader="key5=value5"
resp["Set-Cookie"] = "key5=value5" # $ headerWriteName="Set-Cookie" headerWriteValue="key5=value5" CookieWrite CookieRawHeader="key5=value5" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(key="key6", value="value6", secure=True, httponly=False, samesite="None") # $ CookieWrite CookieName="key6" CookieValue="value6" CookieSecure=true CookieHttpOnly=false CookieSameSite=None
kwargs = {'secure': True}
resp.set_cookie(key="key7", value="value7", **kwargs) # $ CookieWrite CookieName="key7" CookieValue="value7"
return resp

View File

@@ -9,10 +9,11 @@ app = FastAPI()
@app.get("/response_parameter") # $ routeSetup="/response_parameter"
async def response_parameter(response: Response): # $ requestHandler
response.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
response.set_cookie(key="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
response.headers.append("Set-Cookie", "key2=value2") # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
response.headers.append(key="Set-Cookie", value="key2=value2") # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
response.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
response.set_cookie(key="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
response.set_cookie(key="key", value="value", secure=False, httponly=True, samesite="Lax") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=true CookieSameSite=Lax
response.headers.append("Set-Cookie", "key2=value2") # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
response.headers.append(key="Set-Cookie", value="key2=value2") # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
response.headers["X-MyHeader"] = "header-value" # $ headerWriteName="X-MyHeader" headerWriteValue="header-value"
response.status_code = 418
return {"message": "response as parameter"} # $ HttpResponse mimetype=application/json responseBody=Dict
@@ -44,7 +45,7 @@ async def response_parameter_custom_type(response: MyXmlResponse): # $ requestHa
# propagated to the final response though.
print(type(response))
assert type(response) == fastapi.responses.Response
response.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
response.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
response.headers["Custom-Response-Type"] = "yes, but only after function has run" # $ headerWriteName="Custom-Response-Type" headerWriteValue="yes, but only after function has run"
xml_data = "<foo>FOO</foo>"
return xml_data # $ HttpResponse responseBody=xml_data mimetype=application/xml

View File

@@ -203,12 +203,17 @@ def redirect_simple(): # $requestHandler
# Cookies
################################################################################
def unk():
return
@app.route("/setting_cookie") # $routeSetup="/setting_cookie"
def setting_cookie(): # $requestHandler
resp = make_response() # $ HttpResponse mimetype=text/html
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.set_cookie(key="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.headers.add("Set-Cookie", "key2=value2") # $ headerWriteNameUnsanitized="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(key="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(key="key", value="value", secure=True, httponly=True, samesite="Strict") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=true CookieHttpOnly=true CookieSameSite=Strict
resp.set_cookie(key="key", value="value", secure=unk(), httponly=unk(), samesite=unk()) # $ CookieWrite CookieName="key" CookieValue="value"
resp.headers.add("Set-Cookie", "key2=value2") # $ headerWriteNameUnsanitized="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.delete_cookie("key3") # $ CookieWrite CookieName="key3"
resp.delete_cookie(key="key3") # $ CookieWrite CookieName="key3"
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp

View File

@@ -86,8 +86,9 @@ def test2(request): # $ requestHandler
def test3(ctx, req): # $ requestHandler
ensure_tainted(req) # $ tainted
resp = req.response # $ HttpResponse mimetype=text/html
resp.set_cookie("hi", "there") # $ CookieWrite CookieName="hi" CookieValue="there"
resp.set_cookie(value="there", name="hi") # $ CookieWrite CookieName="hi" CookieValue="there"
resp.set_cookie("hi", "there") # $ CookieWrite CookieName="hi" CookieValue="there" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(value="there", name="hi") # $ CookieWrite CookieName="hi" CookieValue="there" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie("hi", "there", secure=True, httponly=True, samesite="Strict") # $ CookieWrite CookieName="hi" CookieValue="there" CookieSecure=true CookieHttpOnly=true CookieSameSite=Strict
return "Ok" # $ HttpResponse responseBody="Ok" mimetype=text/html
@view_config(route_name="test4", renderer="string") # $ routeSetup

View File

@@ -15,8 +15,8 @@ def plain_text_response(request): # $ requestHandler
# this response is not the standard way to use the Djagno REST framework, but it
# certainly is possible -- notice that the response contains double quotes
data = 'this response will contain double quotes since it was a string'
resp = Response(data, None, None, None, None, "text/plain") # $ HttpResponse mimetype=text/plain responseBody=data
resp = Response(data=data, content_type="text/plain") # $ HttpResponse mimetype=text/plain responseBody=data
resp = Response(data, None, None, None, None, "text/plain") # $ HttpResponse responseBody=data mimetype=text/plain
resp = Response(data=data, content_type="text/plain") # $ HttpResponse responseBody=data mimetype=text/plain
return resp
################################################################################
@@ -26,9 +26,9 @@ def plain_text_response(request): # $ requestHandler
@api_view
def setting_cookie(request):
resp = Response() # $ HttpResponse
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.set_cookie(key="key4", value="value") # $ CookieWrite CookieName="key4" CookieValue="value"
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(key="key4", value="value") # $ CookieWrite CookieName="key4" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.cookies["key3"] = "value3" # $ CookieWrite CookieName="key3" CookieValue="value3"
resp.delete_cookie("key4") # $ CookieWrite CookieName="key4"
resp.delete_cookie(key="key4") # $ CookieWrite CookieName="key4"

View File

@@ -68,9 +68,9 @@ def function_based_view(request: Request): # $ requestHandler
@api_view(["GET", "POST"])
def cookie_test(request: Request): # $ requestHandler
resp = Response("wat") # $ HttpResponse
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.set_cookie(key="key4", value="value") # $ CookieWrite CookieName="key4" CookieValue="value"
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.set_cookie(key="key4", value="value") # $ CookieWrite CookieName="key4" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
resp.cookies["key3"] = "value3" # $ CookieWrite CookieName="key3" CookieValue="value3"
return resp

View File

@@ -65,12 +65,13 @@ class ExampleConnectionWrite(tornado.web.RequestHandler):
class CookieWriting(tornado.web.RequestHandler):
def get(self): # $ requestHandler
self.write("foo") # $ HttpResponse mimetype=text/html responseBody="foo"
self.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
self.set_cookie(name="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
self.set_header("Set-Cookie", "key2=value2") # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
self.add_header("Set-Cookie", "key3=value3") # $ headerWriteName="Set-Cookie" headerWriteValue="key3=value3" CookieWrite CookieRawHeader="key3=value3"
self.request.headers.add("Set-Cookie", "key4=value4") # $ headerWriteName="Set-Cookie" headerWriteValue="key4=value4" CookieWrite CookieRawHeader="key4=value4"
self.request.headers["Set-Cookie"] = "key5=value5" # $ headerWriteName="Set-Cookie" headerWriteValue="key5=value5" CookieWrite CookieRawHeader="key5=value5"
self.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
self.set_cookie(name="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
self.set_cookie("key", "value", secure=True, httponly=True, samesite="strict") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=true CookieHttpOnly=true CookieSameSite=Strict
self.set_header("Set-Cookie", "key2=value2") # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
self.add_header("Set-Cookie", "key3=value3") # $ headerWriteName="Set-Cookie" headerWriteValue="key3=value3" CookieWrite CookieRawHeader="key3=value3" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
self.request.headers.add("Set-Cookie", "key4=value4") # $ headerWriteName="Set-Cookie" headerWriteValue="key4=value4" CookieWrite CookieRawHeader="key4=value4" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
self.request.headers["Set-Cookie"] = "key5=value5" # $ headerWriteName="Set-Cookie" headerWriteValue="key5=value5" CookieWrite CookieRawHeader="key5=value5" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
def make_app():

View File

@@ -51,13 +51,14 @@ class CookieWriting(Resource):
"""Examples of providing values in response that is not in the body
"""
def render_GET(self, request: Request): # $ requestHandler
request.addCookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
request.addCookie(k="key", v="value") # $ CookieWrite CookieName="key" CookieValue="value"
request.addCookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
request.addCookie(k="key", v="value") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
request.addCookie("key", "value", secure=True, httponly=True, samesite="strict") # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=true CookieHttpOnly=true CookieSameSite=Strict
val = "key2=value"
request.cookies.append(val) # $ CookieWrite CookieRawHeader=val
request.cookies.append(val) # $ CookieWrite CookieRawHeader=val CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
request.responseHeaders.addRawHeader("key", "value")
request.setHeader("Set-Cookie", "key3=value3") # $ MISSING: CookieWrite CookieRawHeader="key3=value3"
request.setHeader("Set-Cookie", "key3=value3") # $ MISSING: CookieWrite CookieRawHeader="key3=value3" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax
return b"" # $ HttpResponse mimetype=text/html responseBody=b""

View File

@@ -0,0 +1,10 @@
| test.py:10:5:10:37 | ControlFlowNode for Attribute() | Cookie is added without the Secure and HttpOnly attributes properly set. |
| test.py:11:5:11:50 | ControlFlowNode for Attribute() | Cookie is added without the HttpOnly attribute properly set. |
| test.py:12:5:12:52 | ControlFlowNode for Attribute() | Cookie is added without the Secure attribute properly set. |
| test.py:13:5:13:56 | ControlFlowNode for Attribute() | Cookie is added without the Secure and HttpOnly attributes properly set. |
| test.py:14:5:14:53 | ControlFlowNode for Attribute() | Cookie is added without the Secure and HttpOnly attributes properly set. |
| test.py:15:5:15:54 | ControlFlowNode for Attribute() | Cookie is added without the Secure, HttpOnly, and SameSite attributes properly set. |
| test.py:16:5:16:69 | ControlFlowNode for Attribute() | Cookie is added without the HttpOnly attribute properly set. |
| test.py:17:5:17:71 | ControlFlowNode for Attribute() | Cookie is added without the Secure attribute properly set. |
| test.py:18:5:18:67 | ControlFlowNode for Attribute() | Cookie is added without the HttpOnly and SameSite attributes properly set. |
| test.py:19:5:19:69 | ControlFlowNode for Attribute() | Cookie is added without the Secure and SameSite attributes properly set. |

View File

@@ -0,0 +1 @@
Security/CWE-614/InsecureCookie.ql

View File

@@ -0,0 +1,20 @@
from flask import Flask, request, make_response
import lxml.etree
import markupsafe
app = Flask(__name__)
@app.route("/test")
def test():
resp = make_response()
resp.set_cookie("key1", "value1")
resp.set_cookie("key2", "value2", secure=True)
resp.set_cookie("key2", "value2", httponly=True)
resp.set_cookie("key2", "value2", samesite="Strict")
resp.set_cookie("key2", "value2", samesite="Lax")
resp.set_cookie("key2", "value2", samesite="None")
resp.set_cookie("key2", "value2", secure=True, samesite="Strict")
resp.set_cookie("key2", "value2", httponly=True, samesite="Strict")
resp.set_cookie("key2", "value2", secure=True, samesite="None")
resp.set_cookie("key2", "value2", httponly=True, samesite="None")
resp.set_cookie("key2", "value2", secure=True, httponly=True, samesite="Strict")