QL教程3-抓住纵火犯
前言
在我们使用codeql
进行代码审计之前,不妨先学习一些QL
的基础语法,磨刀不误砍柴工。
官方教程链接:https://codeql.github.com/docs/writing-codeql-queries/ql-tutorials/
在这个教程中我们作为一个侦探,为了解决遇到的问题使用QL
进行相应的调查
抓住纵火犯
了解QL谓词和类,以解决作为侦探的第二个谜团
正当我们成功找到小偷并将金冠归还城堡的时候,村庄内又发生了另外一桩可怕的罪行。一大早,几个人在村北的一块地里面纵火,烧毁了所有的庄稼!
现在的我们因为之前找回王冠的事件,已经在村里面拥有了QL侦探专家的声誉,因此我们被村民们恳求再次找到罪魁祸首。
这一次,我们有一些额外的信息,村庄的南北之间存在着强烈的竞争,因此纵火犯住在南方。
阅读以下示例以了解如何在 QL 中定义谓词和类。这些使我们的查询逻辑更容易理解,并有助于简化侦探工作。
选择南方人
这一次我们只需要考虑特定的村民群体,即居住在村庄南部的村民,因此我们可以定义一个新的isSouthern
谓词getLocation() = "south"
:
predicate isSouthern(Person p) {
p.getLocation() = "south"
}
谓词isSouthern(p)
采用单个参数p
并检查p
是否满足属性p.getLocation()='south'
Note:
- 谓词的名称总是以小写字母开头
- 我们还可以用结果定义谓词,在这种情况下,关键字
predicate
被替换为结果的类型,即我们写函数的时候的返回结果result
的类型。
int getAge(){
result=...
}
现在我们可以列出所有的南方人(南方人震怒)
import tutorial
predicate isSouthern(Person p) {
p.getLocation()="south"
}
from Person p
where isSouthern(p)
select p
这是一个简化逻辑的好方法,但我们可以继续优化。目前的逻辑是查看每一个人,然后将其筛选限制为我们所需要的,即isSouthern(p)
。与此相反的是,我们可以定义一个新的类Southerner
,其中直接包含我们想要考虑的人
class Southerner extends Person {
Southerner() { isSouthern(this) }
}
这里即面向对象里面的继承和多态
QL中的类表示一个逻辑属性:当一个值满足该属性时,它就是该类的成员。也就是说,一个值可以在许多类中(在一个特定的类中并不能阻止它也在其他类中)
该表达式isSouthern(this)
定义了类表示的逻辑属性,称为特征谓词
。它使用一个特殊变量this
,如果该属性isSouthern(this)
成立,则该Person
是南方人
Note:
- 如果你熟悉面向对象的编程,你可能会秦湘语将特征谓词视为一个类的构造函数。然后事实并非如此,它是一个不创建任何对象的逻辑属性
我们总是需要在现有的(更大的)类中定义一个新的满足我们需要的类。(可以理解为,表达宽泛的类大多数时候被我们所继承,而我们创建的新类中会表现出多态),在我们的例子中,Southerner
是一种特殊的Person
,因为Person
的范围太大了,所以当我们需要找到范围相对小的南方人的时候,需要继承Person
并筛选。
使用这个类,我们现在可以简单快速的得到所有生活在南方的人
from Southerner s
select s
可能你现在已经注意到,有些谓词是附加的,例如p.getAge()
,而另外有一些则不是,例如isSouthern(p)
。这是因为getAge()
是成员谓词,即只适用于类成员的谓词。我们可以在类中定义这样的成员谓词。在这种情况下,getAge()
在Person
类中被定义。与此相反,isSouthern
是单独定义的,不在任何类中。如果有过面向对象编程的同学这里应该很容易理解。
成员谓词特别有用,因为我们可以轻松地将它们链接在一起。例如,p.getAge().sqrt()
首先获取年龄,然后计算该数字的平方根
旅行限制
我们要考虑的另外一个因素是:当王冠被盗之后村落实施的旅行限制。
最初,村民在村内的出行没有限制。因此,该谓词适用于任何人和任何地区,以下查询列出了所有村民,因为他们都可以前往北方:isAllowdIn(string region)
from Person p
where p.isAllowedIn("north")
select p
然而,在最近的盗窃事件发生后,村民们对潜伏在村子周围的犯罪分子更加担心,他们不再允许10岁以下的儿童外出旅行。
这意味着isAllowedIn(string region)
不再适用于所有人和所有地区,因此如果是孩子,我们应该暂时覆盖原始谓词。
首先定义一个Child
包含所有10岁以下村民的类,然后将其重新定义为成员谓词,以保证孩子仅在自己的区域内移动,这由region = this.getLocation()
表示
class Child extends Person{
Child(){ this.getAge()<10}
override predicate isAllowedIn(string region) {
region=this.getLocation()
}
}
现在尝试将isAllowedIn(string region)
应用到村民身上,如果村民不是孩子,那么原始的定义就会生效,当村民是孩子时,原始的定义就会被重写,即isAllowedIn
我们知道纵火犯住在南方,他们一定能够到北方旅行(这样才能够成功纵火)。编写QL查询以查找可能的嫌疑人,我们还可以扩展select
条件来列出嫌疑人的年龄,这样你就可以清楚地看到所有的孩子都已经从列表中排除。
import tutorial
predicate isSouthern(Person p) {
p.getLocation()="south"
}
class Southerner extends Person{
Southerner(){isSouthern(this)}
}
class Child extends Person{
Child(){ this.getAge()<10}
override predicate isAllowedIn(string region) {
region=this.getLocation()
}
}
from Southerner s
where s.isAllowedIn("north")
select s,s.getAge()
现在我们可以继续收集更多线索,找出到底是哪一个嫌疑人引发了火灾
识别秃头罪犯
当我们询问北方村落的人们有关纵火犯的更多信息,幸运的是,住在田边的农民看到火灾刚开始的时候有两个人逃跑。他只看到他们的头顶,发现他们都是秃头。
这是一个非常有用的线索,于是我们编写了一个QL查询来选择所有秃头的人
from Southerner s
where s.isAllowedIn("north") and not exists( string temp | s.getHairColor()=temp )
select s,s.getAge()
当然我们可以将其封装一下
predicate isBald(Person p) {
not exists(string c | p.getHairColor()=c )
}
最后调用即可
from Southerner s
where s.isAllowedIn("north") and isBald(s)
select s,s.getAge()
通过这个查询我们来选择允许进入北方的秃头南方人
yes!我们现在已经找到了两个纵火犯!他们被村民们逮捕了,在村民中你的声望更高了 😃
END
建了一个微信的安全交流群,欢迎添加我微信备注进群
,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注 😃