https://github.com/0x727/FingerprintHub
name: 0example
priority: 3
nuclei_tags:
- []
fingerprint:
- path: /
request_method: get
request_headers: {}
request_data: ''
status_code: 0
headers: {}
keyword:
- <title>Example Domain</title>
favicon_hash: []
name
和priority
放进单个指纹中。{
"path": "/",
"request_method": "get",
"request_headers": {},
"request_data": "",
"status_code": 0,
"headers": {},
"keyword": [
"<title>Example Domain</title>"
],
"favicon_hash": [],
"priority": 3,
"name": "0example"
},
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct V3WebFingerPrint {
#[serde(default)]
pub name: String,
#[serde(default)]
pub priority: u32,
pub request: WebFingerPrintRequest,
pub match_rules: WebFingerPrintMatch,
}
favicon_hash
来分类到:首页匹配
,图标匹配
和自定义匹配
flowchart TD
subgraph gg[结束]
结束
end
subgraph request [请求逻辑]
目标([目标])-->判断协议{是否有协议判断}
判断协议-->|有| 首页请求[首页请求]
判断协议-->|没有| 添加协议[[添加HTTPS协议]]
添加协议-->首页请求
首页请求-->是否有响应{是否有响应}
是否有响应-->|HTTPS没有响应| 添加HTTP协议[[换HTTP协议]]
添加HTTP协议-->首页请求
是否有响应-->|有响应| 生成匹配数据[[生成匹配数据]]
是否有响应-->|没有响应| 结束([结束])
生成匹配数据-->是否有跳转{是否有跳转}
是否有跳转-->|有跳转|是否超过最大跳转次数{是否超过最大跳转次数}
是否超过最大跳转次数-->|没有|继续请求[[继续请求]]
继续请求[[继续请求]]-->是否有响应{是否有响应}
是否有跳转-->|没有跳转|匹配数据列表[(添加匹配数据到列表)]
是否超过最大跳转次数 -->|超过最大跳转次数| 匹配数据列表[(添加匹配数据到列表)]
end
subgraph matcher [Web匹配逻辑]
匹配数据列表-->是否有图标Hash{是否有图标Hash}
是否有图标Hash-->|有| 匹配图标Hash指纹[[匹配图标Hash指纹]]
是否有图标Hash-->|没有| 匹配图标关键词指纹[[匹配图标关键词指纹]]
匹配图标Hash指纹-->是否有交集{是否有交集}
是否有交集-->|真|匹配到Web指纹
是否有交集-->|假|跳过
匹配图标关键词指纹-->短路求值{短路求值}
短路求值-->|真|匹配到Web指纹
短路求值-->|假|跳过
跳过-->结束
end
subgraph info [补充指纹信息]
匹配到Web指纹-->是否开启服务识别{是否开启服务识别}
是否开启服务识别-->|开启|服务识别[[补充服务信息]]
是否开启服务识别-->|没有开启|是否调用nuclei{是否调用nuclei}
服务识别-->是否调用nuclei{是否调用nuclei}
是否调用nuclei-->|调用|调用nuclei[[调用nuclei]]
是否调用nuclei-->|没有调用|结果队列[(结果队列)]
调用nuclei-->结果队列[(结果队列)]
end
subgraph result [结果处理]
结果队列-->是否开启Webhook{是否开启Webhook}
是否开启Webhook-->|开启|调用结果到Webhook服务器[[调用结果到Webhook服务器]]
是否开启Webhook-->|没有开启|是否需要保持到文件{是否需要保持到文件}
调用结果到Webhook服务器-->是否需要保持到文件{是否需要保持到文件}
是否需要保持到文件-->|是|保存到文件[[保存到文件]]
保存到文件-->打印结果到屏幕[[打印结果到屏幕]]
是否需要保持到文件-->|否|打印结果到屏幕[[打印结果到屏幕]]
打印结果到屏幕[[打印结果到屏幕]]-->结束([结束])
end
响应体和响应头中的关键词
,响应状态码
,图标Hash
组成的数据结构。pub struct RawData {
pub url: Url,
pub path: String,
pub headers: reqwest::header::HeaderMap,
pub status_code: reqwest::StatusCode,
pub text: String,
pub favicon: HashMap<String, String>,
pub next_url: Option<Url>,
}
跳转URL
,图标URL
和网页标题
的正则表达式title
标签和meta
标签的content内容。pub fn get_title(text: &str) -> String {
for titles in Document::from(text).find(Name("title")) {
if !titles.text().is_empty() {
return titles.text();
}
}
for titles in Document::from(text).find(Name("meta")) {
if titles.attr("property") == Some("title") {
return titles.attr("content").unwrap_or_default().to_string();
}
}
String::new()
}
fn get_favicon_link(text: &str, base_url: &Url) -> HashSet<Url> {
let mut icon_links = HashSet::new();
for links in Document::from(text).find(Name("link")) {
if let (Some(rel), Some(href)) = (links.attr("rel"), links.attr("href")) {
if ["icon", "shortcut icon"].contains(&rel) {
if href.starts_with("http://") || href.starts_with("https://") {
let favicon_url = Url::parse(href).unwrap_or_else(|_| base_url.clone());
icon_links.insert(favicon_url);
} else {
let favicon_url = base_url.join(href).unwrap_or_else(|_| base_url.clone());
icon_links.insert(favicon_url);
}
}
}
}
if let Ok(favicon_url) = base_url.join("/favicon.ico") {
icon_links.insert(favicon_url);
}
icon_links
}
LOCATION
跳转链接,然后判断meta标签是否有http-equiv属性为的refresh的再回去url属性得到跳转地址,剩下的就只有正则匹配获取了。 fn get_next_jump(headers: &HeaderMap, url: &Url, text: &str) -> Option<Url> {
let mut next_url_list = Vec::new();
if let Some(location) = headers
.get(LOCATION)
.and_then(|location| location.to_str().ok())
{
next_url_list.push(location.to_string());
}
if next_url_list.is_empty() {
for metas in Document::from(text).find(Name("meta")) {
if let (Some(url),Some(http_equiv)) = (metas.attr("url"),metas.attr("http-equiv")) {
if http_equiv == "refresh"{
next_url_list.push(url.to_string());
}
}
}
}
if next_url_list.is_empty() && text.len() <= 1024 {
for reg in RE_COMPILE_BY_JUMP.iter() {
if let Some(x) = reg.captures(text) {
let mut u = x.name("name").map_or("", |m| m.as_str()).to_string();
u = u.replace('\\'', "").replace('\\"', "");
next_url_list.push(u);
}
}
}
if let Some(next_url) = next_url_list.into_iter().next() {
return if next_url.starts_with("http://") || next_url.starts_with("https://") {
match Url::parse(&next_url) {
Ok(next_path) => Some(next_path),
Err(_) => None,
}
} else if let Ok(next_path) = url.join(&next_url) {
Some(next_path)
} else {
None
};
};
None
}
lazy_static! { static ref RE_COMPILE_BY_CHARSET: Regex = Regex::new(r#"(?im)charset="(.*?)"|charset=(.*?)""#).expect("RE_COMPILE_BY_CHARSET");}
charset
获取编码类型,没有就从请求头中的CONTENT_TYPE
中获取,都没有默认utf-8#[tokio::test]
async fn test_send_requests() {
let test_url = Url::parse("<https://httpbin.org/>").unwrap();
let fingerprint = WebFingerPrintRequest {
path: String::from("/"),
request_method: String::from("GET"),
request_headers: Default::default(),
request_data: Str
ing::from(""),
};
let timeout = 10_u64;
let request_config = RequestOption::new(&timeout, "");
let res = send_requests(&test_url, &fingerprint, &request_config)
.await
.unwrap();
assert!(res.text().await.unwrap().contains("swagger-ui"));
}
https://github.com/0x727/FingerprintHub
pre-commit.yml
格式化检查yaml文件,重新生成web_fingerprint_v3.json
指纹文件和nuclei的对应关系nuclei-templates
项目的插件,根据nuclei和指纹组件的对应关系将插件复制到以组件名为目录。https://github.com/0x727/ObserverWard
dependabot.yml
依赖库版本过低提醒,会自动提交PR,又会触发上面的测试CI,当全部测试通过后,可以合并更新最新依赖库。比封我号的机器人好多了!post-release.yml
当我发布版本打tag标签时触发CI根据CHANGELOG.md
生成版本描述并且创建release
编译和发布版本。flowchart TD
subgraph add [添加指纹]
打开issue添加指纹([打开issue添加指纹])-->解析issue内容{是否符合格式要求}
解析issue内容-->|符合| 验证指纹[验证指纹]
解析issue内容-->|不符合| 提示并且关闭issue[[提示并且关闭issue]]
提示并且关闭issue-->gg[[结束]]
验证指纹-->是否识别成功{是否识别成功}
是否识别成功-->|是| 待审核标签[[待审核标签]]
是否识别成功-->|否| 修改yaml指纹[[评论修改yaml指纹]]
修改yaml指纹-->验证指纹[[验证指纹]]
待审核标签-->是否审核通过[[是否审核通过]]
是否审核通过-->|是| 通过标签[[通过标签]]
是否审核通过-->|否| 原因[[原因]]
通过标签-->合并请求[[合并请求]]
end