Skip to content

Commit

Permalink
新增功能:首页favicon查询支持文件拖拽
Browse files Browse the repository at this point in the history
optimize & bugfix:
1. 勾选证书查询后数据一直不显示
2. 部分证书域名查询后不显示
3. 多次查询后点击查询无响应 fixes #87
  • Loading branch information
flashine committed Apr 8, 2022
1 parent ddb3852 commit 77f3d76
Show file tree
Hide file tree
Showing 16 changed files with 199 additions and 128 deletions.
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
<h1 align="center">Fofa_Viewer 🔗 </h1>
<p align="center">
<img src="https://img.shields.io/badge/JDK-1.8-green">
<img src="https://img.shields.io/badge/JDK-11-green">
<img src="https://img.shields.io/badge/version-1.1.5-brightgreen">
<img src="https://img.shields.io/badge/author-f1ashine-orange">
<img src="https://img.shields.io/badge/WgpSec-%E7%8B%BC%E7%BB%84%E5%AE%89%E5%85%A8%E5%9B%A2%E9%98%9F-blue">
</p>

<img src="https://socialify.git.ci/wgpsec/fofa_viewer/image?font=Bitter&forks=1&issues=1&language=1&logo=https%3A%2F%2Fwww.wgpsec.org%2F_nuxt%2Fimg%2Fbanner.91d92b1.svg&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light">

<p align="center">
<a href="https://github.com/wgpsec/fofa_viewer/releases">release下载</a>
·
<a href="https://github.com/wgpsec/fofa_viewer/issues">反馈Bug</a>
·
<a href="https://github.com/wgpsec/fofa_viewer/issues">提交需求</a>
</p>

中文 | [EN](README.en.md)

## 简介

Fofa_Viewer: 一个简单易用的fofa客户端,由 WgpSec狼组安全团队 [**f1ashine**](https://github.com/f1ashine) 师傅主要开发。程序使用 JavaFX 编写,便于跨平台使用
Fofa_Viewer: 一个简单易用的fofa客户端,由 WgpSec狼组安全团队 [**f1ashine**](https://github.com/f1ashine) 师傅主要开发。程序使用 JavaFX 编写,便于跨平台使用

## 使用说明
本工具基于 FoFa 的 API 进行封装,使用时需要高级会员或者普通会员的 API key使用注册用户的 API key 会提示账户需要充值F币。
本工具基于 FoFa 的 API 进行封装,使用时需要高级会员或者普通会员的 API key使用注册用户的 API key 会提示账户需要充值F币。

点击 https://github.com/wgpsec/fofa_viewer/releases 下载

Expand Down Expand Up @@ -75,7 +76,13 @@ VIP说明:https://fofa.info/static_pages/vip
本工具使用的是`jsoup`对网页进行解析,对于使用Vue一类构建的网站只能通过chrome进行js解析后获取link标签然后获取favicon地址,对于这种情况目前只能将favicon的链接粘贴到首页进行查询。

4. IP和端口两个一起排序?
先点击IP或者端口这一列的header进行排序,然后按住shift点击另一列就可以一起排序了
先点击IP或者端口这一列的header进行排序,然后按住shift点击另一列就可以一起排序了。

5. 为什么勾选title后查不到数据?
目前部分fofa数据较为敏感,因此在查询数据较多且敏感数据也在查到的数据中时可能就会因为数据敏感导致所有数据都不显示了,建议可以设置`maxsize=100`再勾选title进行查询,另外同时勾选title和证书查询可能也会存在查不到数据的情况,建议两者只勾选一个。

6. 为什么使用普通会员的API查询会报错?
因为fofa官方限制了普通会员单次查询的最大数量为100,因此当maxsize超过100时就会报错,显示`401 Unauthorized`

## :rocket: 二次开发
```
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/fofaviewer/bean/RequestBean.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fofaviewer.bean;

import javafx.scene.control.Tab;
import org.fofaviewer.request.RequestStatus;

import java.util.HashMap;
Expand All @@ -8,6 +9,7 @@ public class RequestBean {
private String requestUrl;
private String tabTitle;
private String size;
private Tab tab;
private RequestStatus requestStatus = RequestStatus.READY;
private HashMap<String, String> result;

Expand All @@ -17,6 +19,14 @@ public RequestBean(String requestUrl, String tabTitle, String size) {
this.size = size;
}

public Tab getTab() {
return tab;
}

public void setTab(Tab tab) {
this.tab = tab;
}

public String getRequestUrl() {
return requestUrl;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.fofaviewer.callback;

import javafx.scene.control.TableView;
import java.util.List;

/**
* MainController回调,用于在线程中设置调用MainController的方法
Expand All @@ -9,7 +10,7 @@ public interface MainControllerCallback {

default boolean getFidStatus() {return false;}

default void queryCall(String queryTxt){}
default void queryCall(List<String> strList){}

default void addSBListener(TableView<?> view){}

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/fofaviewer/callback/RequestCallback.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ public interface RequestCallback<T> {
/**
* 请求之前先添加tab
*/
default void before(TabDataBean tabDataBean){}
default void before(TabDataBean tabDataBean, RequestBean bean){}
/**
* 请求成功
*/
default void succeeded(BorderPane tablePane, StatusBar bar){}
default void succeeded(BorderPane tablePane, StatusBar bar, RequestBean bean){}

/**
* 请求失败
*/
default void failed(String text){}
default void failed(String text, RequestBean bean){}

/**
* 暂停线程
Expand Down
161 changes: 104 additions & 57 deletions src/main/java/org/fofaviewer/controllers/MainController.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.DirectoryChooser;
Expand All @@ -44,6 +43,8 @@
import java.io.*;
import java.math.BigInteger;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -140,8 +141,8 @@ private void initialize() {
this.client = DataUtil.loadConfigure();
this.tabPane.setCallback(new MainControllerCallback() {
@Override
public void queryCall(String queryTxt) {
query(queryTxt);
public void queryCall(List<String> strList) {
query(strList);
}
});
//初始化起始页tab
Expand All @@ -152,6 +153,25 @@ public void queryCall(String queryTxt) {
Label faviconLabel = new Label(resourceBundle.getString("FAVICON_LABEL"));
TextField tf = TextFields.createClearableTextField();
TextField favionTF = TextFields.createClearableTextField();
// 设置从文件导入favicon
favionTF.setOnDragOver(event -> {
if (event.getGestureSource() != favionTF){
event.acceptTransferModes(TransferMode.ANY);
}
});
favionTF.setOnDragDropped(event -> {
Dragboard dragboard = event.getDragboard();
if (dragboard.hasFiles()){
try {
File file = dragboard.getFiles().get(0);
if (file != null && file.exists()) {
favionTF.setText(file.getAbsolutePath());
}
}catch (Exception e){
Logger.error(e.toString());
}
}
});
// Image image = new Image("api_doc_en.png");
ImageView view = new ImageView(new Image(Locale.getDefault().getLanguage().equals(Locale.CHINESE.getLanguage()) ? "/images/api_doc_cn.png" : "/images/api_doc_en.png"));
ScrollPane scrollPane = new ScrollPane();
Expand All @@ -171,21 +191,36 @@ public void queryCall(String queryTxt) {
if(!txt.isEmpty()){
String serialnumber = txt.replaceAll(" ", "");
BigInteger i = new BigInteger(serialnumber, 16);
query("cert=\"" + i + "\"");
query(new ArrayList<String>(){{add("cert=\"" + i + "\"");}});
}
});
queryFavicon.setOnAction(event -> {
String url = favionTF.getText().trim();
if(!url.isEmpty()){
if(!url.startsWith("http")){
DataUtil.showAlert(Alert.AlertType.ERROR, null, resourceBundle.getString("ERROR_URL")).showAndWait();
}else {
HashMap<String,String> res = helper.getImageFavicon(url);
String text = favionTF.getText().trim();
if(!text.isEmpty()){
if(!text.startsWith("http")){ // 文件导入
String suffix = text.substring(text.lastIndexOf(".")+1).toLowerCase();
try {
byte[] content = Files.readAllBytes(Paths.get(text));
switch (suffix){
case "jpg": case "png": case "ico": case "svg":
String encode = java.util.Base64.getMimeEncoder().encodeToString(content);
query(new ArrayList<String>(){{add("icon_hash=\"" + helper.getIconHash(encode) + "\"");}});
break;
default:
DataUtil.showAlert(Alert.AlertType.ERROR, null, resourceBundle.getString("ERROR_FILE")).showAndWait();
break;
}
} catch (IOException e) {
DataUtil.showAlert(Alert.AlertType.ERROR, null, resourceBundle.getString("ERROR_FILE")).showAndWait();
Logger.error(e);
}
}else { // url导入
HashMap<String,String> res = helper.getImageFavicon(text);
if(res != null){
if(res.get("code").equals("error")){
DataUtil.showAlert(Alert.AlertType.ERROR, null, res.get("msg")).showAndWait();return;
}
query(res.get("msg"));
query(new ArrayList<String>(){{add(res.get("msg"));}});
}
}
}
Expand Down Expand Up @@ -219,12 +254,14 @@ public void openFile(String fileName){
try {
FileInputStream inputStream = new FileInputStream(fileName);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
List<String> list = new ArrayList<>();
String str;
while((str = bufferedReader.readLine()) != null) {
if(!str.equals("")){
query(str);
list.add(str);
}
}
query(list);
} catch (IOException e) {
Logger.error(e);
}
Expand All @@ -250,7 +287,7 @@ private void getQueryAPI(){
@FXML
private void queryAction(){
if(queryTF.getText() != null){
query(queryTF.getText());
query(new ArrayList<String>(){{add(queryTF.getText());}});
}
}

Expand Down Expand Up @@ -504,31 +541,46 @@ protected Void call() {
/**
* 处理查询结果
*/
public void query(String text){
String tabTitle = text.trim();
if(text.startsWith("(*)")){
tabTitle = text;
text = text.substring(3);
text = "(" + text + ") && (is_honeypot=false && is_fraud=false)";
}
if(checkHoneyPot.isSelected() && !text.contains("(is_honeypot=false && is_fraud=false)")){
tabTitle = "(*)" + text;
text = "(" + text + ") && (is_honeypot=false && is_fraud=false)";
}
final String queryText = text;
if(this.tabPane.isExistTab(tabTitle)){ // 若已存在同名Tab 则直接跳转,不查询
this.tabPane.setCurrentTab(this.tabPane.getTab(tabTitle));
return;
}
for(CheckBox box : keyMap.keySet()){
String name = keyMap.get(box);
if(box.isSelected()){
if(!client.fields.contains(name)){
client.fields.add(name);
public void query(List<String> strList){
ArrayList<RequestBean> beans = new ArrayList<>();
for(String text:strList) {
String tabTitle = text.trim();
if (text.startsWith("(*)")) {
tabTitle = text;
text = text.substring(3);
text = "(" + text + ") && (is_honeypot=false && is_fraud=false)";
}
if (checkHoneyPot.isSelected() && !text.contains("(is_honeypot=false && is_fraud=false)")) {
tabTitle = "(*)" + text;
text = "(" + text + ") && (is_honeypot=false && is_fraud=false)";
}
final String queryText = text;
if (this.tabPane.isExistTab(tabTitle)) { // 若已存在同名Tab 则直接跳转,不查询
if(strList.size() == 1){
this.tabPane.setCurrentTab(this.tabPane.getTab(tabTitle));
return;
}else{
continue;
}
}else{
client.fields.remove(name);
}
for (CheckBox box : keyMap.keySet()) {
String name = keyMap.get(box);
if (box.isSelected()) {
if (!client.fields.contains(name)) {
client.fields.add(name);
}
} else {
client.fields.remove(name);
}
}
Tab tab = new Tab();
tab.setOnCloseRequest(event -> tabPane.closeTab(tab));
tab.setText(tabTitle);
tab.setTooltip(new Tooltip(tabTitle));
String url = client.getParam(null, isAll.isSelected()) + helper.encode(queryText);
RequestBean bean = new RequestBean(url, tabTitle, client.getSize());
bean.setTab(tab);
beans.add(bean);
}
MainControllerCallback mCallback = new MainControllerCallback() {
@Override
Expand All @@ -537,46 +589,41 @@ public boolean getFidStatus() {
}

@Override
public void queryCall(String queryTxt) {
query(queryTxt);
public void queryCall(List<String> strList) {
query(strList);
}

@Override
public void addSBListener(TableView<?> view) {
addScrollBarListener(view);
}
};
Tab tab = new Tab();
tab.setOnCloseRequest(event -> tabPane.closeTab(tab));
tab.setText(tabTitle);
tab.setTooltip(new Tooltip(tabTitle));
String url = client.getParam(null, isAll.isSelected()) + helper.encode(queryText);
RequestBean bean = new RequestBean(url, tabTitle, client.getSize());
new Request(new ArrayList<RequestBean>(){{add(bean);}}, new RequestCallback<Request>() {

new Request(beans, new RequestCallback<Request>() {
@Override
public void before(TabDataBean tabDataBean) {
tabPane.addTab(tab, tabDataBean, url);
tabPane.setCurrentTab(tab);
public void before(TabDataBean tabDataBean, RequestBean bean) {
tabPane.addTab(bean.getTab(), tabDataBean, bean.getRequestUrl());
tabPane.setCurrentTab(bean.getTab());
LoadingPane ld = new LoadingPane();
tab.setContent(ld);
bean.getTab().setContent(ld);
}

@Override
public void succeeded(BorderPane tablePane, StatusBar bar) {
public void succeeded(BorderPane tablePane, StatusBar bar, RequestBean bean) {
if (bean.getResult().get("code").equals("200")) {
tab.setContent(tablePane);
bean.getTab().setContent(tablePane);
decoratedField.addLog(bean.getTabTitle());
tabPane.addBar(tab, bar);
tabPane.addBar(bean.getTab(), bar);
} else {
((LoadingPane)tab.getContent()).setErrorText("请求状态码:"+bean.getResult().get("code")+ bean.getResult().get("msg"));
((LoadingPane)bean.getTab().getContent()).setErrorText("请求状态码:"+bean.getResult().get("code")+ bean.getResult().get("msg"));
}
}

@Override
public void failed(String text) { // 网络问题请求失败
((LoadingPane)tab.getContent()).setErrorText(text);
public void failed(String text, RequestBean bean) { // 网络问题请求失败
((LoadingPane) bean.getTab().getContent()).setErrorText(text);
}
}, mCallback).query();
}, mCallback).query(beans.size());
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/fofaviewer/controls/CloseableTabPane.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,17 @@ public CloseableTabPane() {
dataMap.clear();
tabPane.getTabs().add(tab);
});
//重新加载tab
MenuItem reload = new MenuItem(ResourceBundleUtil.getResource().getString("RELOAD_TAB"));
reload.setOnAction(e->{
Tab tab = this.tabPane.getSelectionModel().getSelectedItem();
if(tab.getText().equals(homepage)){
return;
}
String queryTxT = this.queryMap.get(tab);
this.tabPane.getTabs().remove(tab);
closeTab(tab);
callback.queryCall(queryTxT);
callback.queryCall(new ArrayList<String>(){{add(queryTxT);}});
});
menuButton.getItems().addAll(closeOthers, closeSelected, closeAll, reload);
sp.getChildren().add(menuButton);
Expand Down
Loading

0 comments on commit 77f3d76

Please sign in to comment.