Yak:专注安全能力融合的编程语言快速入门
大家好,今天给大家分享一门专为安全领域设计、致力于解决安全能力融合问题的编程语言------Yak。在安全研发领域,我们常面临这样的困境:不同安全能力模块因适配不同语言而分散,导致平台与能力割裂,规模化研发和效率提升受阻。而Yak语言的核心目标,就是打造"一站式"安全能力基座,打破这种壁垒。接下来,我将结合官方文档的核心内容,带大家全面认识Yak,快速掌握其基础用法与核心特性。

一、Yak要解决的核心问题
提到黑客编程,Python凭借简洁语法和丰富安全工具生态,成为安全从业者的必备技能。但随着技术深入,规模化工具/平台/安全产品的研发需求凸显,Golang因更高效率、更适配产品分发和工程研发的特性,逐渐成为安全研发的重要选择。
在这一阶段,"安全研发不仅包含安全平台研发,也包含安全能力研发"的理念逐渐普及。我们通常会用合适的语言编写平台处理业务需求,但安全能力研发更为复杂,不同安全工具往往采用"最合适"的语言实现------这就造成了安全平台与安全能力模块的割裂。
这里的"最合适",很大程度上源于历史原因:缺乏专人进行新场景适配,导致"老代码"不断积累,最终形成能力分散的局面。为解决这一问题,Yak应运而生,其核心职责就是安全能力融合------无论是PoC、扫描器、扫描模块还是漏洞扫描算法,都能通过Yak实现,最终打造"一站式"安全能力基座。
二、核心理念:安全基础能力融合
Yak的核心理念围绕"安全基础能力融合"展开,具体具备以下优势:
- 完善的内容生态,提供入门/保姆级安全研发教程;
- 长期支持,具备成功的企业实践经验;
- 高级功能自由度极高,提供独一无二的Fuzz体验;
- 底层融合多种安全能力/工具,打破工具和安全小领域间的壁垒;
- 集成MIT协议的高质量工具;
- 最终目标是提升行业整体安全水平。
三、速览:极速编写安全工具
Yak的一大优势是极简的安全工具编写体验,我们以服务扫描工具为例,快速感受其便捷性。
创建文件 service_scan.yak,内容如下:
yak
// 极简获取参数,--target xxxx --port 80
scanTarget, scanPorts = cli.String("target"), cli.String("port")
// 默认批量进行服务扫描
results, err = servicescan.Scan(scanTarget, scanPorts)
die(err)
// 取出扫描结果(异步扫描结果)
for result = range results {
println(result.String())
}
执行命令:
bash
yak service_scan.yak --target 192.168.1.1/24 --port 22,80
即可得到扫描输出(部分结果):
tcp://192.168.1.32:22 open openssh[6.6.1]
tcp://192.168.1.21:22 open openssh[7.4]
tcp://192.168.1.40:22 open openssh[6.6.1]
tcp://192.168.1.43:22 open openssh[5.3]
tcp://192.168.1.83:80 open apache_tomcat[1.1]/coyote[1.1]/coyote_http_connector[1.1]/java[*]/jquery[*]/jquery[1.3.2]
tcp://192.168.1.99:80 open
tcp://192.168.1.122:80 open nginx[*]
tcp://192.168.1.125:80 open linux_kernel[*]/nginx[1.10.3]/ubuntu[*]/ubuntu_linux[*]
tcp://192.168.1.126:80 open nginx[*]/php[5.4.45]
仅需几行代码,就能实现批量服务扫描,足见Yak在安全工具开发中的高效性。
四、Yak基础语法快速入门
在正式学习前,先明确几个约定:后续文中"Yak"和"Yaklang"均指代Yak语言;完全大写的"YAK"表示Yak生态,包含Yaklang编程语言和Yakit安全平台。
4.1 第一个Yak程序:Hello World
和多数编程语言一样,Yak的第一个程序可实现"Hello World"打印,仅需一行代码:
yak
print("Hello World")
// 输出:Hello World
Yak设计遵循"符合逻辑"的原则:无需引入额外库,无需封装成类,无需在代码末尾加分号,极简的语法降低了入门门槛。
4.2 变量创建
Yak支持多种变量创建方式,核心逻辑简洁直观:
yak
var myVariable = 1 // 声明并初始化变量
myVariable = 2 // 赋值(变量已存在则更新值,不存在则创建)
myVariable := 3 // 强制创建新变量(与var myVariable = 3等价)
var myAnotherVariable // 声明空变量,值为nil
注意:Yak是动态类型语言,无需指定变量类型,会自动根据赋值内容推断类型,因此创建变量时不能指定类型。
4.3 基本数据类型(字面量)
Yak中的"值(字面量)"是基本数据类型,变量是值的容器。常见基本类型及创建方式如下:
yak
myIntVariable = 1 // 整数
myFloatVariable = 3.14 // 浮点数
myStringVariable = "Hello World" // 字符串
myBoolVariable = true // 布尔值
myArrayVariable = [1, 2, 3] // 数组
myDictVariable = {"key": "value"} // 字典
myFunctionVariable = func() { // 函数
print("Hello World")
}
Yak的设计贴合"符合直觉"的哲学,上述代码无需额外解释即可轻松理解。
4.4 重要类型:string(字符串)
字符串在安全研发中应用广泛,数据传输和存储多以字符串形式进行。以下是Yak中字符串的核心操作。
4.4.1 字符串创建
支持三种创建方式,适配不同场景需求:
yak
// 1. 双引号(支持转义,如\"、\n)
myString1 := "Hello \"Yak\""
// 2. 反引号(支持多行,不转义,不能包含反引号)
myString2 := `Hello Yak
This is a multi-line string`
// 3. Heredoc语法(支持多行,可包含反引号)
myString3 := <<<EOF
Hello Yak
This is Heredoc string
EOF
4.4.2 字符串格式化与插值
Yak提供多种字符串格式化方式,适配不同变量数量和使用习惯:
yak
// 1. % 语法(单变量)
name := "John"
println("Hello %v" % name) // 输出:Hello John
// 2. % 语法(多变量,需用[]包裹)
name, age := "John", 20
println("Hello %v, you are %v years old" % [name, age]) // 输出:Hello John, you are 20 years old
// 3. sprintf函数(多变量直接传入参数)
println(sprintf("Hello %v, you are %v years old", name, age)) // 输出同上
// 4. f-string插值(简洁直观,常用)
println(f"Hello ${name}, you are ${age} years old") // 输出同上
说明 :%v是通用占位符,可适配任意类型变量;也可使用%d(整数)、%f(浮点数)、%s(字符串)等指定类型占位符。
4.5 复合类型:列表与字典
列表(数组)和字典是Yak中常用的复合类型,支持"增删改查"等核心操作,语法简洁。
4.5.1 列表操作
yak
myList = [1,2,3]
// 增:Append方法
myList.Append(4)
println(myList) // 输出:[1 2 3 4]
// 删:Remove方法(删除指定元素)
myList.Remove(2)
println(myList) // 输出:[1 3 4]
// 改:通过索引赋值
myList[1] = 999
println(myList) // 输出:[1 999 4]
// 查:索引访问与切片
println(myList[2]) // 输出:4(访问索引2的元素)
println(myList[:2]) // 输出:[1 999](从开头到索引2前)
println(myList[1:]) // 输出:[999 4](从索引1到末尾)
println(myList[1:3]) // 输出:[999 4](从索引1到索引3前)
// 内置append函数(批量添加元素)
newList = append(myList, 5, 6, 7)
println(newList) // 输出:[1 999 4 5 6 7]
4.5.2 字典操作
yak
myDict = {}
// 增:键值对赋值
myDict["name"] = "John"
myDict["age"] = 12
println(myDict) // 输出:map[age:12 name:John]
// 删:Delete方法
myDict.Delete("age")
println(myDict) // 输出:map[name:John]
// 改:覆盖键值对
myDict["name"] = "Tom"
myDict["age"] = 22
println(myDict) // 输出:map[age:22 name:Tom]
// 查:键访问与字符串插值
println(f`Hello ${myDict["name"]}, your age is ${myDict["age"]}`) // 输出:Hello Tom, your age is 22
4.6 控制流
Yak通过IF、Switch实现条件控制,通过For实现循环控制,语法贴近主流编程语言,学习成本低。
4.6.1 条件控制(IF/ELSE)
支持elif或else if表示"否则如果",两种写法等价,可按需选择:
yak
// 示例1:elif用法
scores = [10, 20, 30, 40, 50, 60, 70, 80, 99, 100]
teamScore = 0
for score in scores {
if score > 90 {
teamScore += 3
} elif score > 80 {
teamScore += 2
} elif score > 70 {
teamScore += 1
} else {
teamScore += 0
}
}
println(teamScore) // 输出:7
// 示例2:else if用法(与elif等价)
result = ""
age = 18
if age > 80 {
result = "old man"
} else if age > 10 {
result = "teenager"
} else {
result = "child"
}
println(result) // 输出:teenager
4.6.2 循环控制(For)
Yak的For循环支持多种写法,适配不同循环场景:
yak
scores = [10, 20, 30, 40, 50, 60, 70, 80, 99, 100]
// 1. foreach循环(Python风格)
for score in scores {
println(score) // 依次输出列表中所有元素
}
// 2. for range循环(Golang风格,获取索引和值)
for index, score = range scores {
println(index, score) // 输出:索引 对应值(如0 10、1 20...)
}
// 3. while循环(通过for condition实现)
i := 0
for i < 10 {
println(i) // 输出0-9
i += 1
}
// 4. 三段式For循环(贴近Golang/C风格)
for i := 0; i < 10; i++ {
println(i) // 输出0-9
}
4.7 函数与函数调用
Yak中函数创建灵活,支持多种关键字和简写形式,适配不同编程习惯。
4.7.1 基础函数创建与调用
支持func、fn、def三种关键字创建函数,功能完全等价:
yak
// 1. func关键字
func myFunction() {
println("Hello World")
}
myFunction() // 输出:Hello World
// 2. fn关键字
fn helloName(name) {
return sprintf("Hello %v", name)
}
println(helloName("John")) // 输出:Hello John
// 3. def关键字
def helloNameAndAge(name, age) {
return sprintf("Hello %v, you are %v years old", name, age)
}
println(helloNameAndAge("John", 20)) // 输出:Hello John, you are 20 years old
4.7.2 箭头函数(简写形式)
通过=>创建箭头函数,语法更简洁,支持自动返回表达式结果:
yak
// 基础箭头函数
myFunction = () => {
println("Hello World")
}
myFunction() // 输出:Hello World
// 单参数可省略括号,表达式自动返回
helloName = name => sprintf("Hello %v", name)
println(helloName("John")) // 输出:Hello John
// 多参数箭头函数
helloNameAndAge = (name, age) => {
return sprintf("Hello %v, you are %v years old", name, age)
}
println(helloNameAndAge("John", 20)) // 输出:Hello John, you are 20 years old
说明:箭头右侧若为单个表达式,会自动返回表达式结果;若为代码块,会自动返回最后一个表达式的值。
4.7.3 函数与闭包
Yak函数支持自动捕获和访问外部变量,这一特性称为"闭包",极大提升了函数式编程的表现力:
yak
// 捕获外部变量
name := "John"
helloWithOutterName = () => sprintf("Hello %v", name)
println(helloWithOutterName()) // 输出:Hello John
// 修改外部变量
name := "John"
helloModifiedWithOutterName = () => {
name = "Jane"
return sprintf("Hello %v", name)
}
println(helloModifiedWithOutterName()) // 输出:Hello Jane
4.8 库函数的使用
Yak作为安全领域DSL(领域特定语言),内置了大量安全相关库函数,无需安装依赖、无需导入,可直接使用。以服务扫描函数servicescan.Scan为例:
yak
results, err = servicescan.Scan(scanTarget, scanPorts)
die(err) // 错误处理
for result in results {
println(result.String()) // 打印扫描结果
}
类似的内置安全库函数还有很多,例如:
synscan.Scan:启动SYN端口开放扫描;mitm.Start:启动MITM劫持服务器。
提示 :这些库函数多基于Golang实现,返回值常包含error类型,因此调用时需进行错误处理(下文将详细讲解)。
4.9 错误处理
Yak提供多种错误处理方式,适配不同场景需求,确保程序健壮性。
4.9.1 手动处理错误(die(err))
通过手动接收函数返回的error,再用die(err)处理:若存在错误,程序中断并抛出错误;无错误则正常执行。
yak
results, err = servicescan.Scan(scanTarget, scanPorts)
die(err) // 有错误则中断,无错误则继续
4.9.2 自动处理错误(~语法,WavyCall)
使用~语法可自动处理错误,效果与die(err)一致,语法更简洁:
yak
// 自动处理错误,有错误则中断执行
results = servicescan.Scan(scanTarget, scanPorts)~
说明 :无论函数返回值最后一个是否为error类型,均可使用~语法;若函数报错,~会抛出错误并中断当前函数执行。
4.9.3 Try-Catch捕获错误
支持try-catch-finally语法,可主动捕获错误并自定义处理逻辑:
yak
try {
results = servicescan.Scan(scanTarget, scanPorts)~
for result in results {
println(result.String())
}
} catch err { // 捕获错误,err为错误变量(不可加括号)
println(err) // 自定义错误处理
} finally {
println("finally") // 无论是否出错,均会执行
}
4.9.4 defer recover()捕获错误
通过defer+recover()捕获错误,适用于自定义函数或需要保证执行完整性的场景:
yak
myFunc = () => {
// 延迟执行错误捕获逻辑
defer func {
err = recover()
if err != nil {
println(err) // 打印错误
}
}
println("Before Error")
1/0 // 触发错误(除零错误)
println("After Error") // 不会执行
}
myFunc()
// 输出:
// Before Error
// runtime error: integer divide by zero
提示 :~语法抛出的错误,也可被defer recover()或try-catch捕获,实际开发中可组合使用。
4.10 并发编程
Yak支持并发编程,语法与Golang类似,通过go关键字创建并发任务,高效利用系统资源。
4.10.1 基础并发任务(go关键字)
yak
go func() {
println("Hello World in Goroutine")
}()
sleep(1) // 等待并发任务执行完成
println("Hello World in Main")
// 输出:
// Hello World in Goroutine
// Hello World in Main
4.10.2 WaitGroup:等待并发任务完成
当需要等待所有并发任务执行完毕后再继续主程序时,可使用WaitGroup:
yak
wg = sync.NewWaitGroup()
for element in [1,2,3] {
element := element // 捕获循环变量
wg.Add(1) // 计数器+1,标记一个并发任务启动
go func() {
defer wg.Done() // 任务完成,计数器-1
println(element)
} ()
}
wg.Wait() // 等待所有任务完成(计数器归0)
println("All Goroutine is done")
// 输出:1、2、3(顺序不固定)、All Goroutine is done
4.10.3 SizedWaitGroup:限制并发资源
WaitGroup计数器无上限,可能导致资源耗尽。SizedWaitGroup可指定最大并发数,限制资源使用:
yak
// 指定最大并发数为2
wg = sync.NewSizedWaitGroup(2)
for element in [1,2,3] {
element := element
wg.Add(1) // 若计数器已达上限,会阻塞至有任务完成
go func() {
defer wg.Done()
println(element)
sleep(1) // 模拟任务耗时
} ()
}
wg.Wait()
println("All Goroutine is done")
说明 :当SizedWaitGroup计数器达到指定上限时,新的wg.Add(1)会阻塞,直到有任务完成(计数器减少)后才会继续执行。
五、总结
通过本文的分享,相信大家对Yak语言有了全面的认识。Yak作为专注于安全能力融合的编程语言,核心价值在于打破安全平台与安全能力模块的割裂,提供"一站式"安全能力基座,极大提升安全研发的效率和规模化水平。
其核心优势可总结为三点:一是语法简洁直观,兼容多语言编程习惯,入门门槛低;二是内置丰富安全库函数,无需额外依赖,可快速开发安全工具;三是支持灵活的错误处理和并发编程,适配复杂安全研发场景。
对于安全从业者而言,Yak不仅是一门新的编程语言,更是提升安全研发效率、整合安全能力的有力工具。后续我会继续分享Yak的高级特性(如Fuzz功能)和实战案例,感兴趣的朋友可以持续关注。如果在学习过程中有疑问,欢迎在评论区交流讨论!