Skip to content

Commit c6c1ebe

Browse files
committed
Merge remote-tracking branch 'upstream/master' into typeAheadSink
2 parents d212394 + f958916 commit c6c1ebe

File tree

38 files changed

+726
-136
lines changed

38 files changed

+726
-136
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
## New queries
1414

15-
| **Query** | **Tags** | **Purpose** |
16-
|---------------------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
17-
15+
| **Query** | **Tags** | **Purpose** |
16+
|---------------------------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
17+
| Cross-site scripting through exception (`js/xss-through-exception`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights potential XSS vulnerabilities where an exception is written to the DOM. Results are not shown on LGTM by default. |
1818

1919
## Changes to existing queries
2020

@@ -25,3 +25,6 @@
2525
| Unbound event handler receiver (`js/unbound-event-handler-receiver`) | Fewer false positive results | This query now recognizes additional ways event handler receivers can be bound. |
2626

2727
## Changes to libraries
28+
29+
* The predicates `RegExpTerm.getSuccessor` and `RegExpTerm.getPredecessor` have been changed to reflect textual, not operational, matching order. This only makes a difference in lookbehind assertions, which are operationally matched backwards. Previously, `getSuccessor` would mimick this, so in an assertion `(?<=ab)` the term `b` would be considered the predecessor, not the successor, of `a`. Textually, however, `a` is still matched before `b`, and this is the order we now follow.
30+

cpp/ql/src/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.ql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,18 @@ predicate hasZeroParamDecl(Function f) {
8484
}
8585

8686
// True if this file (or header) was compiled as a C file
87-
predicate isCompiledAsC(Function f) {
88-
exists(File file | file.compiledAsC() |
89-
file = f.getFile() or file.getAnIncludedFile+() = f.getFile()
90-
)
87+
predicate isCompiledAsC(File f) {
88+
f.compiledAsC()
89+
or
90+
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
9191
}
9292

9393
from FunctionCall fc, Function f, Parameter p
9494
where
9595
f = fc.getTarget() and
9696
p = f.getAParameter() and
9797
hasZeroParamDecl(f) and
98-
isCompiledAsC(f) and
98+
isCompiledAsC(f.getFile()) and
9999
not f.isVarargs() and
100100
not f instanceof BuiltInFunction and
101101
p.getIndex() < fc.getNumberOfArguments() and

cpp/ql/src/Likely Bugs/Underspecified Functions/TooFewArguments.ql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ predicate hasZeroParamDecl(Function f) {
2424
}
2525

2626
// True if this file (or header) was compiled as a C file
27-
predicate isCompiledAsC(Function f) {
28-
exists(File file | file.compiledAsC() |
29-
file = f.getFile() or file.getAnIncludedFile+() = f.getFile()
30-
)
27+
predicate isCompiledAsC(File f) {
28+
f.compiledAsC()
29+
or
30+
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
3131
}
3232

3333
from FunctionCall fc, Function f
@@ -36,7 +36,7 @@ where
3636
not f.isVarargs() and
3737
not f instanceof BuiltInFunction and
3838
hasZeroParamDecl(f) and
39-
isCompiledAsC(f) and
39+
isCompiledAsC(f.getFile()) and
4040
// There is an explicit declaration of the function whose parameter count is larger
4141
// than the number of call arguments
4242
exists(FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() |

cpp/ql/src/Likely Bugs/Underspecified Functions/TooManyArguments.ql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@ predicate hasZeroParamDecl(Function f) {
2525
}
2626

2727
// True if this file (or header) was compiled as a C file
28-
predicate isCompiledAsC(Function f) {
29-
exists(File file | file.compiledAsC() |
30-
file = f.getFile() or file.getAnIncludedFile+() = f.getFile()
31-
)
28+
predicate isCompiledAsC(File f) {
29+
f.compiledAsC()
30+
or
31+
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
3232
}
3333

3434
from FunctionCall fc, Function f
3535
where
3636
f = fc.getTarget() and
3737
not f.isVarargs() and
3838
hasZeroParamDecl(f) and
39-
isCompiledAsC(f) and
39+
isCompiledAsC(f.getFile()) and
4040
exists(f.getBlock()) and
4141
// There must not exist a declaration with the number of parameters
4242
// at least as large as the number of call arguments

javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import com.semmle.util.process.Env;
2222
import com.semmle.util.projectstructure.ProjectLayout;
2323
import com.semmle.util.trap.TrapWriter;
24+
import java.io.BufferedReader;
2425
import java.io.File;
2526
import java.io.IOException;
27+
import java.io.InputStreamReader;
2628
import java.io.Reader;
2729
import java.lang.ProcessBuilder.Redirect;
2830
import java.net.URI;
@@ -195,6 +197,11 @@ public class AutoBuild {
195197
private final String defaultEncoding;
196198
private ExecutorService threadPool;
197199
private volatile boolean seenCode = false;
200+
private boolean installDependencies = false;
201+
private int installDependenciesTimeout;
202+
203+
/** The default timeout when running <code>yarn</code>, in milliseconds. */
204+
public static final int INSTALL_DEPENDENCIES_DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10 minutes
198205

199206
public AutoBuild() {
200207
this.LGTM_SRC = toRealPath(getPathFromEnvVar("LGTM_SRC"));
@@ -204,6 +211,11 @@ public AutoBuild() {
204211
this.typeScriptMode =
205212
getEnumFromEnvVar("LGTM_INDEX_TYPESCRIPT", TypeScriptMode.class, TypeScriptMode.FULL);
206213
this.defaultEncoding = getEnvVar("LGTM_INDEX_DEFAULT_ENCODING");
214+
this.installDependencies = Boolean.valueOf(getEnvVar("LGTM_INDEX_TYPESCRIPT_INSTALL_DEPS"));
215+
this.installDependenciesTimeout =
216+
Env.systemEnv()
217+
.getInt(
218+
"LGTM_INDEX_TYPESCRIPT_INSTALL_DEPS_TIMEOUT", INSTALL_DEPENDENCIES_DEFAULT_TIMEOUT);
207219
setupFileTypes();
208220
setupXmlMode();
209221
setupMatchers();
@@ -533,6 +545,10 @@ private void extractSource() throws IOException {
533545
List<Path> tsconfigFiles = new ArrayList<>();
534546
findFilesToExtract(defaultExtractor, filesToExtract, tsconfigFiles);
535547

548+
if (!tsconfigFiles.isEmpty() && this.installDependencies) {
549+
this.installDependencies(filesToExtract);
550+
}
551+
536552
// extract TypeScript projects and files
537553
Set<Path> extractedFiles = extractTypeScript(defaultExtractor, filesToExtract, tsconfigFiles);
538554

@@ -549,6 +565,61 @@ private void extractSource() throws IOException {
549565
}
550566
}
551567

568+
/** Returns true if yarn is installed, otherwise prints a warning and returns false. */
569+
private boolean verifyYarnInstallation() {
570+
ProcessBuilder pb = new ProcessBuilder(Arrays.asList("yarn", "-v"));
571+
try {
572+
Process process = pb.start();
573+
boolean completed = process.waitFor(this.installDependenciesTimeout, TimeUnit.MILLISECONDS);
574+
if (!completed) {
575+
System.err.println("Yarn could not be launched. Timeout during 'yarn -v'.");
576+
return false;
577+
}
578+
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
579+
String version = reader.readLine();
580+
System.out.println("Found yarn version: " + version);
581+
return true;
582+
} catch (IOException | InterruptedException ex) {
583+
System.err.println(
584+
"Yarn not found. Please put 'yarn' on the PATH for automatic dependency installation.");
585+
Exceptions.ignore(ex, "Continue without dependency installation");
586+
return false;
587+
}
588+
}
589+
590+
protected void installDependencies(Set<Path> filesToExtract) {
591+
if (!verifyYarnInstallation()) {
592+
return;
593+
}
594+
for (Path file : filesToExtract) {
595+
if (file.getFileName().toString().equals("package.json")) {
596+
System.out.println("Installing dependencies from " + file);
597+
ProcessBuilder pb =
598+
new ProcessBuilder(
599+
Arrays.asList(
600+
"yarn",
601+
"install",
602+
"--verbose",
603+
"--non-interactive",
604+
"--ignore-scripts",
605+
"--ignore-platform",
606+
"--ignore-engines",
607+
"--ignore-optional",
608+
"--no-default-rc",
609+
"--no-bin-links",
610+
"--pure-lockfile"));
611+
pb.directory(file.getParent().toFile());
612+
pb.redirectOutput(Redirect.INHERIT);
613+
pb.redirectError(Redirect.INHERIT);
614+
try {
615+
pb.start().waitFor(this.installDependenciesTimeout, TimeUnit.MILLISECONDS);
616+
} catch (IOException | InterruptedException ex) {
617+
throw new ResourceError("Could not install dependencies from " + file, ex);
618+
}
619+
}
620+
}
621+
}
622+
552623
private ExtractorConfig mkExtractorConfig() {
553624
ExtractorConfig config = new ExtractorConfig(true);
554625
config = config.withSourceType(getSourceType());

javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ public void extractTypeScriptFiles(
128128
}
129129
}
130130

131+
@Override
132+
protected void installDependencies(Set<Path> filesToExtract) {
133+
// never install dependencies during testing
134+
}
135+
131136
@Override
132137
protected void extractXml() throws IOException {
133138
Files.walkFileTree(
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Directly writing exceptions to a webpage without sanitization allows for a cross-site scripting
9+
vulnerability if the value of the exception can be influenced by a user.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
To guard against cross-site scripting, consider using contextual output encoding/escaping before
16+
writing user input to the page, or one of the other solutions that are mentioned in the
17+
references.
18+
</p>
19+
</recommendation>
20+
21+
<example>
22+
<p>
23+
The following example shows an exception being written directly to the document,
24+
and this exception can potentially be influenced by the page URL,
25+
leaving the website vulnerable to cross-site scripting.
26+
</p>
27+
<sample src="examples/ExceptionXss.js" />
28+
</example>
29+
30+
<references>
31+
<li>
32+
OWASP:
33+
<a href="https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html">DOM based
34+
XSS Prevention Cheat Sheet</a>.
35+
</li>
36+
<li>
37+
OWASP:
38+
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">XSS
39+
(Cross Site Scripting) Prevention Cheat Sheet</a>.
40+
</li>
41+
<li>
42+
OWASP
43+
<a href="https://www.owasp.org/index.php/DOM_Based_XSS">DOM Based XSS</a>.
44+
</li>
45+
<li>
46+
OWASP
47+
<a href="https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting">Types of Cross-Site
48+
Scripting</a>.
49+
</li>
50+
<li>
51+
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
52+
</li>
53+
</references>
54+
</qhelp>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name Cross-site scripting through exception
3+
* @description Inserting data from an exception containing user
4+
* input into the DOM may enable cross-site scripting.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision medium
8+
* @id js/xss-through-exception
9+
* @tags security
10+
* external/cwe/cwe-079
11+
* external/cwe/cwe-116
12+
*/
13+
14+
import javascript
15+
import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss
16+
import DataFlow::PathGraph
17+
18+
from
19+
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
20+
where
21+
cfg.hasFlowPath(source, sink)
22+
select sink.getNode(), source, sink,
23+
sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
24+
"user-provided value"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function setLanguageOptions() {
2+
var href = document.location.href,
3+
deflt = href.substring(href.indexOf("default=")+8);
4+
5+
try {
6+
var parsed = unknownParseFunction(deflt);
7+
} catch(e) {
8+
document.write("Had an error: " + e + ".");
9+
}
10+
}

javascript/ql/src/semmle/javascript/Expr.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,21 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
245245
ctx.(ConditionalExpr).inNullSensitiveContext()
246246
)
247247
}
248+
249+
/**
250+
* Gets the data-flow node where exceptions thrown by this expression will
251+
* propagate if this expression causes an exception to be thrown.
252+
*/
253+
DataFlow::Node getExceptionTarget() {
254+
if exists(this.getEnclosingStmt().getEnclosingTryCatchStmt())
255+
then
256+
result = DataFlow::parameterNode(this
257+
.getEnclosingStmt()
258+
.getEnclosingTryCatchStmt()
259+
.getACatchClause()
260+
.getAParameter())
261+
else result = any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn()
262+
}
248263
}
249264

250265
/**

0 commit comments

Comments
 (0)