Skip to content

Commit a09acb5

Browse files
author
Alvaro Muñoz
committed
Better parsing of Bash script commands
1 parent c7b57b5 commit a09acb5

3 files changed

Lines changed: 90 additions & 24 deletions

File tree

ql/lib/codeql/actions/Bash.qll

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,21 @@ module Bash {
88

99
string commandSeparator() { result = ["&&", "||"] }
1010

11-
string pipeSeparator() { result = "|" }
12-
13-
string splitSeparators() {
14-
result = stmtSeparator() or result = commandSeparator() or result = pipeSeparator()
11+
string splitSeparator() {
12+
result = stmtSeparator() or
13+
result = commandSeparator()
1514
}
1615

1716
string redirectionSeparator() { result = [">", ">>", "2>", "2>>", ">&", "2>&", "<", "<<<"] }
1817

18+
string pipeSeparator() { result = "|" }
19+
20+
string separator() {
21+
result = stmtSeparator() or
22+
result = commandSeparator() or
23+
result = pipeSeparator()
24+
}
25+
1926
string partialFileContentCommand() { result = ["cat", "jq", "yq", "tail", "head"] }
2027

2128
/** Checks if expr is a bash command substitution */

ql/lib/codeql/actions/ast/internal/Ast.qll

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,39 +1438,43 @@ class RunImpl extends StepImpl {
14381438
max(int round, string new | this.doReplaceQuotedStrings(i, round, _, new) | new order by round)
14391439
}
14401440

1441-
private string cmdProducer(int i) {
1442-
result = this.quotedStringLineProducer(i).splitAt(Bash::splitSeparators()).trim() and
1441+
private string stmtProducer(int i) {
1442+
result = this.quotedStringLineProducer(i).splitAt(Bash::splitSeparator()).trim() and
14431443
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
14441444
// we only one the split parts that do not contain any of the separators
1445-
not result.indexOf(Bash::splitSeparators()) > -1
1445+
not result.indexOf(Bash::splitSeparator()) > -1
14461446
}
14471447

1448-
private predicate doRestoreQuotedStrings(int line, int round, string old, string new) {
1448+
private predicate doStmtRestoreQuotedStrings(int line, int round, string old, string new) {
14491449
round = 0 and
1450-
old = this.cmdProducer(line) and
1450+
old = this.stmtProducer(line) and
14511451
new = old
14521452
or
14531453
round > 0 and
14541454
exists(string middle, string target, string replacement |
1455-
this.doRestoreQuotedStrings(line, round - 1, old, middle) and
1455+
this.doStmtRestoreQuotedStrings(line, round - 1, old, middle) and
14561456
this.rankedQuotedStringReplacements(round, target, replacement) and
14571457
new = middle.replaceAll(replacement, target)
14581458
)
14591459
}
14601460

1461-
private string restoredQuotedStringLineProducer(int i) {
1461+
private string restoredStmtQuotedStringLineProducer(int i) {
14621462
result =
1463-
max(int round, string new | this.doRestoreQuotedStrings(i, round, _, new) | new order by round)
1463+
max(int round, string new |
1464+
this.doStmtRestoreQuotedStrings(i, round, _, new)
1465+
|
1466+
new order by round
1467+
)
14641468
}
14651469

1466-
private predicate doRestoreCmdSubstitutions(int line, int round, string old, string new) {
1470+
private predicate doStmtRestoreCmdSubstitutions(int line, int round, string old, string new) {
14671471
round = 0 and
1468-
old = this.restoredQuotedStringLineProducer(line) and
1472+
old = this.restoredStmtQuotedStringLineProducer(line) and
14691473
new = old
14701474
or
14711475
round > 0 and
14721476
exists(string middle, string target, string replacement |
1473-
this.doRestoreCmdSubstitutions(line, round - 1, old, middle) and
1477+
this.doStmtRestoreCmdSubstitutions(line, round - 1, old, middle) and
14741478
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
14751479
new = middle.replaceAll(replacement, target)
14761480
)
@@ -1479,26 +1483,71 @@ class RunImpl extends StepImpl {
14791483
string getStmt(int i) {
14801484
result =
14811485
max(int round, string new |
1482-
this.doRestoreCmdSubstitutions(i, round, _, new)
1486+
this.doStmtRestoreCmdSubstitutions(i, round, _, new)
14831487
|
14841488
new order by round
14851489
)
14861490
}
14871491

14881492
string getAStmt() { result = this.getStmt(_) }
14891493

1490-
predicate getAssignment(int i, string name, string value) {
1491-
exists(string stmt |
1492-
stmt = this.getStmt(i) and
1493-
name = stmt.regexpCapture("^([a-zA-Z0-9\\-_]+)=.*", 1) and
1494-
value = stmt.regexpCapture("^[a-zA-Z0-9\\-_]+=(.*)", 1)
1494+
private string cmdProducer(int i) {
1495+
result = this.quotedStringLineProducer(i).splitAt(Bash::separator()).trim() and
1496+
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
1497+
// we only one the split parts that do not contain any of the separators
1498+
not result.indexOf(Bash::separator()) > -1
1499+
}
1500+
1501+
private predicate doCmdRestoreQuotedStrings(int line, int round, string old, string new) {
1502+
round = 0 and
1503+
old = this.cmdProducer(line) and
1504+
new = old
1505+
or
1506+
round > 0 and
1507+
exists(string middle, string target, string replacement |
1508+
this.doCmdRestoreQuotedStrings(line, round - 1, old, middle) and
1509+
this.rankedQuotedStringReplacements(round, target, replacement) and
1510+
new = middle.replaceAll(replacement, target)
14951511
)
14961512
}
14971513

1498-
predicate getAnAssignment(string name, string value) { this.getAssignment(_, name, value) }
1514+
private string restoredCmdQuotedStringLineProducer(int i) {
1515+
result =
1516+
max(int round, string new |
1517+
this.doCmdRestoreQuotedStrings(i, round, _, new)
1518+
|
1519+
new order by round
1520+
)
1521+
}
1522+
1523+
private predicate doCmdRestoreCmdSubstitutions(int line, int round, string old, string new) {
1524+
round = 0 and
1525+
old = this.restoredCmdQuotedStringLineProducer(line) and
1526+
new = old
1527+
or
1528+
round > 0 and
1529+
exists(string middle, string target, string replacement |
1530+
this.doCmdRestoreCmdSubstitutions(line, round - 1, old, middle) and
1531+
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
1532+
new = middle.replaceAll(replacement, target)
1533+
)
1534+
}
1535+
1536+
string getCmd(int i) {
1537+
result =
1538+
max(int round, string new |
1539+
this.doCmdRestoreCmdSubstitutions(i, round, _, new)
1540+
|
1541+
new order by round
1542+
)
1543+
}
1544+
1545+
string getACmd() { result = this.getCmd(_) }
14991546

15001547
string getCommand(int i) {
1501-
result = this.getStmt(i) and
1548+
result = this.getCmd(i) and
1549+
// exclude variable declarations
1550+
not result.regexpMatch("^[a-zA-Z0-9\\-_]+=") and
15021551
// exclude the following keywords
15031552
not result =
15041553
[
@@ -1509,6 +1558,16 @@ class RunImpl extends StepImpl {
15091558

15101559
string getACommand() { result = this.getCommand(_) }
15111560

1561+
predicate getAssignment(int i, string name, string value) {
1562+
exists(string stmt |
1563+
stmt = this.getStmt(i) and
1564+
name = stmt.regexpCapture("^([a-zA-Z0-9\\-_]+)=.*", 1) and
1565+
value = stmt.regexpCapture("^[a-zA-Z0-9\\-_]+=(.*)", 1)
1566+
)
1567+
}
1568+
1569+
predicate getAnAssignment(string name, string value) { this.getAssignment(_, name, value) }
1570+
15121571
predicate getAWriteToGitHubEnv(string name, string value) {
15131572
exists(string raw |
15141573
Bash::extractFileWrite(this.getScript(), "GITHUB_ENV", raw) and

ql/lib/codeql/actions/security/PoisonableSteps.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class PoisonableCommandStep extends PoisonableStep, Run {
1818
PoisonableCommandStep() {
1919
exists(string regexp |
2020
poisonableCommandsDataModel(regexp) and
21-
exists(this.getACommand().regexpFind(regexp, _, _))
21+
this.getACommand().regexpMatch("^" + regexp + ".*")
2222
)
2323
}
2424
}

0 commit comments

Comments
 (0)