惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
InfoQ
D
DataBreaches.Net
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
Martin Fowler
Martin Fowler
Security Latest
Security Latest
Cisco Talos Blog
Cisco Talos Blog
MongoDB | Blog
MongoDB | Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园_首页
L
LINUX DO - 最新话题
Know Your Adversary
Know Your Adversary
S
Schneier on Security
The Last Watchdog
The Last Watchdog
Attack and Defense Labs
Attack and Defense Labs
T
Tenable Blog
G
GRAHAM CLULEY
Y
Y Combinator Blog
P
Palo Alto Networks Blog
L
LINUX DO - 热门话题
Hugging Face - Blog
Hugging Face - Blog
W
WeLiveSecurity
C
Cybersecurity and Infrastructure Security Agency CISA
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
The Register - Security
The Register - Security
T
The Exploit Database - CXSecurity.com
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
Cyberwarzone
Cyberwarzone
雷峰网
雷峰网
T
Tailwind CSS Blog
V2EX - 技术
V2EX - 技术
T
Threat Research - Cisco Blogs
S
Secure Thoughts
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
C
Cyber Attacks, Cyber Crime and Cyber Security
The Cloudflare Blog
量子位
Apple Machine Learning Research
Apple Machine Learning Research
T
Threatpost
S
SegmentFault 最新的问题
小众软件
小众软件
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security

LoRexxar's Blog | 信息技术分享

2026年了,核弹还是fastjson,fastjson1.2.83 RCE是怎么回事? Ghost Bits,Java WAF之殇? AI.Re.(3) - AI到底变了什么?为什么突然井喷? AI.Re.(2) - OpenClaw到底为什么爆火? AI.Re.(1) - AI变革的时代来了吗? 不容错过的2025年度漏洞:React2Shell(CVE-2025-55182)分析 PHP CGI Windows平台远程代码执行漏洞(CVE-2024-4577)分析与复现 人与代码的桥梁-聊聊SAST Joern In RealWorld (3) - 致远OA A8 SSRF2RCE Joern In RealWorld (2) - Jumpserver随机数种子泄露导致账户劫持漏洞(CVE-2023-42820) 深入浅出Joern(四)不常用语法大全 Joern In RealWorld (1) - Acutators + CVE-2022-21724 深入浅出Joern(二)CPG与图数据库 深入浅出Joern(一)Joern与CPG是什么? 打造自己的AIGC应用(一)入门篇 赛博偶像速成指南(三)- Midjourney 赛博偶像速成指南(二)- SD进阶篇 从0到1的ChatGPT - 进阶篇(五)- Embeddings 从0到1的ChatGPT - 进阶篇(四)- 训练自己的ChatGPT 看上去不起眼的微信机器人以及公众号爬虫 从0到1的ChatGPT - 进阶篇(三)- ChatGPT+?
深入浅出Joern(三)Joern和Neo4j常用语法大全
LoRexxar · 2023-08-24 · via LoRexxar's Blog | 信息技术分享

前两篇文章主要讲了Joern相关技术的设计原理,以及CPG的实际表现

在研究Joern和Neo4j的过程中,我遇到了一个相当大的问题,就是由于我对OverflowDB包括scala和cypher语言都不熟。Joern和Neo4j分别支持这几种冷门语言,而相应的文档其实没有解决我的问题。

所以在继续研究Joern之前,先花时间简单记录一些Joern和Neo4j实用的语法和范例,给自己当个字典随时可以查阅。

Joern

节点获取

  • cpg.method.name(‘xxx’)
  • cpg.method(‘xxx’)

寻找对应名字的方法定义的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
joern> cpg.method.name("getRequestBody").l
val res4: List[io.shiftleft.codepropertygraph.generated.nodes.Method] = List(
Method(
id = 15337L,
astParentFullName = "<empty>",
astParentType = "<empty>",
code = "public static String getRequestBody(HttpServletRequest request) throws IOException",
columnNumber = Some(value = 5),
columnNumberEnd = Some(value = 5),
filename = "src\\main\\java\\org\\joychou\\util\\WebUtils.java",
fullName = "org.joychou.util.WebUtils.getRequestBody:<unresolvedSignature>(1)",
hash = None,
isExternal = false,
lineNumber = Some(value = 13),
lineNumberEnd = Some(value = 16),
name = "getRequestBody",
order = 1,
signature = "<unresolvedSignature>(1)"
)
)
  • cpg.call.name(‘xxx’)
  • cpg.call(‘xxx’)

寻找对应方法/函数调用的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
joern> cpg.call("getRequestBody").take(1).l
val res7: List[io.shiftleft.codepropertygraph.generated.nodes.Call] = List(
Call(
id = 8485L,
argumentIndex = 2,
argumentName = None,
code = "getRequestBody(request)",
columnNumber = Some(value = 22),
dispatchType = "DYNAMIC_DISPATCH",
dynamicTypeHintFullName = ArraySeq(),
lineNumber = Some(value = 25),
methodFullName = "org.joychou.util.WebUtils.getRequestBody:<unresolvedSignature>(1)",
name = "getRequestBody",
order = 2,
possibleTypes = ArraySeq(),
signature = "<unresolvedSignature>(1)",
typeFullName = "java.lang.String"
)
)
  • cpg.annotation.name(“.*Mapping”)
  • cpg.annotation(“.*Mapping”)

寻找对应名字注解的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
joern> cpg.annotation.name(".*Mapping").take(1).l
val res10: List[io.shiftleft.codepropertygraph.generated.nodes.Annotation] = List(
Annotation(
id = 2532L,
argumentIndex = -1,
argumentName = None,
code = "@RequestMapping(\"/safecode\")",
columnNumber = Some(value = 5),
fullName = "org.springframework.web.bind.annotation.RequestMapping",
lineNumber = Some(value = 20),
name = "RequestMapping",
order = 7
)
)

在joern的节点你都可以非常简单的用点连接来获取对应节点连接的其他节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
joern> cpg.annotation.name(".*Mapping").method.take(1).l
val res21: List[io.shiftleft.codepropertygraph.generated.nodes.Method] = List(
Method(
id = 2498L,
astParentFullName = "<empty>",
astParentType = "<empty>",
code = "public void crlf(HttpServletRequest request, HttpServletResponse response)",
columnNumber = Some(value = 5),
columnNumberEnd = Some(value = 5),
filename = "src\\main\\java\\org\\joychou\\controller\\CRLFInjection.java",
fullName = "org.joychou.controller.CRLFInjection.crlf:<unresolvedSignature>(2)",
hash = None,
isExternal = false,
lineNumber = Some(value = 20),
lineNumberEnd = Some(value = 28),
name = "crlf",
order = 1,
signature = "<unresolvedSignature>(2)"
)
)

除了常规的method,annotation,call这种以外,比较常见的节点类型还有

https://docs.joern.io/cpgql/node-type-steps/

  • cpg.configFile:配置文件
  • cpg.identifier:标识符
  • cpg.imports:引用
  • cpg.methodReturn:方法的返回节点
  • cpg.parameter:参数

当然除了上面的这些节点以外,还有一些调用关系的通用节点

  • cpg.method.name(“getRequestBody”).caller

返回节点列表对应节点的被调用节点,也就是父节点

  • cpg.method.name(“getRequestBody”).callee

返回节点列表对应节点的调用节点,也就是子节点

  • cpg.method.name(“getRequestBody”).callIn

返回节点列表对应父节点的所有节点

过滤器

凡是节点连接的都是作为结果传到下一级的,如果是想筛选符合条件的节点则需要用where或者属性过滤器,比如说

  • cpg.method.name(“getRequestBody”).l

查询名字为getRequestBody,这个name就是属性过滤器,向下一级返回的是符合属性过滤器的method节点

  • cpg.method.where(_.name(“getRequestBody”)).l

或者用where也行,where语句内容会作为筛选条件影响返回的method内容

  • cpg.method.name.l
1
2
3
4
5
joern> cpg.method.name.l
val res36: List[String] = List(
"configure",
"main",
...

如果不是使用()作为属性过滤器,那么返回内容就会直接变成name属性列表。

当然除了where以外,也支持很多种过滤器

  • where,whereNot:筛选返回为空或者非空的节点

    • cpg.method.where(_.isExternal(false)).name.l
  • filter,filterNot:筛选返回为True或者False的节点

    • cpg.method.filter(_.isExternal == false).name.l
  • and,or:多个过滤器之间的关系

返回结果处理

在处理结果返回的时候也有一些方式改变返回的内容。一般来说查询结果会是一个字典列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
joern> cpg.method.name("getRequestBody").l
val res64: List[io.shiftleft.codepropertygraph.generated.nodes.Method] = List(
Method(
id = 15337L,
astParentFullName = "<empty>",
astParentType = "<empty>",
code = "public static String getRequestBody(HttpServletRequest request) throws IOException",
columnNumber = Some(value = 5),
columnNumberEnd = Some(value = 5),
filename = "src\\main\\java\\org\\joychou\\util\\WebUtils.java",
fullName = "org.joychou.util.WebUtils.getRequestBody:<unresolvedSignature>(1)",
hash = None,
isExternal = false,
lineNumber = Some(value = 13),
lineNumberEnd = Some(value = 16),
name = "getRequestBody",
order = 1,
signature = "<unresolvedSignature>(1)"
)
)

可以用map来改变返回的结构,这是一个类似于lambda的语法,会遍历列表的所有节点然后生成结果.

1
2
3
4
5
6
7
8
9
joern> cpg.method.name("getRequestBody").map(n=>List(n.filename, n.lineNumber, n.fullName, n.code)).l
val res66: List[List[String | Option[Integer]]] = List(
List(
"src\\main\\java\\org\\joychou\\util\\WebUtils.java",
Some(value = 13),
"org.joychou.util.WebUtils.getRequestBody:<unresolvedSignature>(1)",
"public static String getRequestBody(HttpServletRequest request) throws IOException"
)
)

除了map以外,另外还有两个实用的

  • .clone,创建一个深复制,是在写比较复杂的脚本时候用到的
  • .dedup,列表内容去重
  • .sideEffect,按照格式要求执行但不改变原列表

重复获取

既然需要寻找两个节点之间的路径,那么就少不了重复,重复获取父级节点就是最简单的一种数据流分析。

  • x.repeat(_.caller)(_.times(5))

重复获取caller共5次,如果找不到结果就会停止

  • x.repeat(_.caller)(_.until(_.name(“foo”)))

重复调用 caller 查询,直到找到一个方法名为 foo 的方法,找不到就返回空。

  • x.repeat(_.caller)(_.emit(_.isMethod).times(5))

emit的意思是会将查询的过程节点作为返回的列表中的一员。

上面这句语句就是指,重复5次获取当前节点的caller的节点属性,除此之外还会带上路径上所有满足isMethod的节点。

格式化

Joern对于返回结果提供了公式化的输出格式,而且如果不指定输出直接就没有返回

  • toList,L,输出列表

img

  • toJson,toJsonPretty,输出json或者格式化的json

img

  • p,browse,输出可读性非常强的结果,如果是流会输出表格

img

  • size,输出节点数量

img

  • dump,dumpRaw,输出节点代码,只有节点才有这个属性

img

数据流分析

1
2
def source = cpg.method.where(_.annotation.name(".*Mapping")).parameter
def sink = cpg.call.name("exec")
  • reachableBy,是最简单的数据流分析函数,他返回的是从前面开始到后面,这个后面节点的位置。比如这里从sink开始查找,展示的就是source的位置

img

  • reachableByFlows,展示两个节点之间的流,包括流上的每个节点

img

Neo4j

Neo4j的语法在我看来要比Joern的语法别扭多了,但有些问题其实在Neo4j会更容易得到答案,可视化的图结构在某些情况下会有非常明显的优势。

创建实体和关系

  • 最简单的创建实体和关系(不带属性)
1
create (n:Person)-[:LOVES]->(m:Dog)
  • 创建2个或多个属性的实体
1
create (z:ziduan{name:"f_name",table:"dianlibiao"}) return count(*)
  • 创建带属性的实体和关系
1
create (n:Person{name:"李四"})-[:FEAR{level:1}]->(t:Tiger{type:"东北虎"})
  • 对实体间创建关系
1
match (n:Person{name:""王五""}), (m:Person{name:"赵六"}) create (n)-[k:KNOW]->(m) return k

delete 删除实体或关系

  • 用match查找已有实体、关系, 再用delete删除关系
1
match (n:Person{name:"李四"})-[f:FEAR]->(t:Tiger) delete f
  • 删除所有节点中的边关系
1
match(m)-[b:bian]-(n) delete b
  • match查询实体,delete删除实体
1
match (n:Person{name:"李四"}) delete n
  • 同时删除实体和关系
1
match(n) detach delete n
  • 删除所有节点
1
match (n) delete n
  • 删除所有节点并级联删除关系
1
match (n) detach delete n
  • 删除Loc标签的所有节点和关系
1
MATCH (r:Loc) DETACH DELETE r

match查询节点和关系

1
2
3
4
5
6
7
match 
(node)-[relationship]->(node)
where
(node | relationship)
return
(node | relationship)
match (n:Persion)-[:HAS_PHONE]->(p:Phone) where n.name="姓名6" return n, p limit 10
  • n,代表Persion的别名
  • :HAS_PHONE,代表前面Persion的关系
  • (),括号里的都是实体
  • [],中括号里的都是关系
  • -,代表无方向的关系
  • ->,代表有方向的关系
  • 查询所有实体节点
1
match(n) return n
  • 根据id查找实体
1
2
match (t:Tiger) where id(t)=1837 return t
match (t:Tiger) where id(t)=1837 delete t
  • 多度关系查询
1
match (n:Persion)-[:HAS_PHONE]->(p:Phone)-[:CALL]->(p1: Persion) where n.name=“姓名6” return n, p,p1  limit 10
  • 利用关系查询, 不限定实体只限定关系的查询
1
match p=()-[c: CALL]->() return p limit 10
  • 根据实体属性匹配正则查询, 使用通配符,通配符前要加~
1
match (n:USERS) where n.name=~'Jack.*' return n limit 10
  • 包含查询 使用关键词contains
1
match (n:USERS) where n.name= contains 'J'  return n limit 10
  • 附带属性多实体查询, 逗号隔开
1
match (n:Person{name:"王五"}), (m:Person{name:"赵六"}) return n,m
  • 查询多种label节点,并进行过滤
1
match(n) where n:标签1 or n:标签B  return distinct n;
  • distinct * 关键字表示返回节点不重复
  • 返回非某几类标签,注意使用 not and 关键字
1
match(n) where not n:标签1 and not n:标签B  return distinct n;

set 修改实体标签或属性

  • 实体增加标签
1
match (t:Tiger) where id(t)=1837 set t:A  return t

本质上是给实体增加一个标签,一个实体可以有多个标签

  • 给实体增加属性
1
match (a:A) where id(t)=1837 set a.年龄=10  return a
  • 给关系增加属性
1
match (n:Person)-[l:LOVE]->(:Person) set l.date="1990" return n, l
  • 给所有节点增加标签
1
match(n) set n:table return n

搜索路径

  • 单条最短路径
1
match (p1:Person{name:"姓名2"}),(p2:Person{name:"姓名10"}), p=shortestpath((p1)-[*..10]-(p2)) return p

shortestpath()用于查询最短路径 [..10] 表示关系中*不超过10度关系**

  • 多条最短路径
1
match (p1:Person{name:"姓名2"}),(p2:Person{name:"姓名10"}), p=allshortestpaths((p1)-[*..10]-(p2)) return p

关系查询

  • merge 有关系则返回,没有则创建关系
1
2
3
match (n:Person{name:"王五"}), (m:Person{name:"赵六"}) merge (n)-[l:LOVE]->(m) return l

match (n),(m) where n=m merge (n)-[t:TABLE{table_name:n.table}]-(m) return t
  • optional match 可选择匹配,若匹配结果包含空,则用NULL占位
1
OPTIONAL MATCH (n)-[r]->(m) RETURN m

匹配结果集中如果有丢的部分,则会用null来补充

XXX with 字符串开头结尾匹配

  • start with 匹配字符串的开头
1
2
3
MATCH (n)
WHERE n.name STARTS WITH '张'
RETURN n
  • end with 匹配字符串的结尾
1
2
3
MATCH (n)
WHERE n.name ENDS WITH '三'
RETURN n

边对应的节点

  • startNode(rel) 得到一条关系rel对应的起始节点
  • endNode(rel) 得到一条关系rel对应的中止节点