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