diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
index 66bbb56..034326f 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
@@ -105,6 +105,24 @@ namespace WelsonJS.Launcher.Properties {
}
}
+ ///
+ /// 과(와) 유사한 지역화된 문자열을 찾습니다.
+ ///
+ internal static string CitiApiKey {
+ get {
+ return ResourceManager.GetString("CitiApiKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// https://api.criminalip.io/v1/과(와) 유사한 지역화된 문자열을 찾습니다.
+ ///
+ internal static string CitiApiPrefix {
+ get {
+ return ResourceManager.GetString("CitiApiPrefix", resourceCulture);
+ }
+ }
+
///
/// https://copilot.microsoft.com/과(와) 유사한 지역화된 문자열을 찾습니다.
///
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
index ed4a71f..88786e0 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
@@ -190,4 +190,10 @@
90
+
+
+
+
+ https://api.criminalip.io/v1/
+
\ No newline at end of file
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs
index d972332..711394a 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs
@@ -56,6 +56,7 @@ namespace WelsonJS.Launcher
_tools.Add(new ResourceTools.Settings(this, _httpClient));
_tools.Add(new ResourceTools.DevTools(this, _httpClient));
_tools.Add(new ResourceTools.DnsQuery(this, _httpClient));
+ _tools.Add(new ResourceTools.CitiQuery(this, _httpClient));
_tools.Add(new ResourceTools.Tfa(this, _httpClient));
_tools.Add(new ResourceTools.Whois(this, _httpClient));
}
@@ -368,6 +369,11 @@ namespace WelsonJS.Launcher
{
data = xmlHeader + "\r\n" + data;
}
+ else if (mimeType == "application/json")
+ {
+ data = xmlHeader + "\r\n";
+ mimeType = "application/xml";
+ }
ServeResource(context, Encoding.UTF8.GetBytes(data), mimeType, statusCode);
}
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/CitiQuery.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/CitiQuery.cs
new file mode 100644
index 0000000..a12e0d8
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/CitiQuery.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Net.Http;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace WelsonJS.Launcher.ResourceTools
+{
+ public class CitiQuery : IResourceTool
+ {
+ private readonly ResourceServer Server;
+ private readonly HttpClient _httpClient;
+ private const string Prefix = "citi-query/";
+
+ public CitiQuery(ResourceServer server, HttpClient httpClient)
+ {
+ Server = server;
+ _httpClient = httpClient;
+ }
+
+ public bool CanHandle(string path)
+ {
+ return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public async Task HandleAsync(HttpListenerContext context, string path)
+ {
+ try
+ {
+ string target = path.Substring(Prefix.Length).Trim();
+ string apiKey = Program.GetAppConfig("CitiApiKey");
+ if (string.IsNullOrEmpty(apiKey))
+ {
+ Server.ServeResource(context, "Missing API key", "application/xml", 500);
+ return;
+ }
+
+ string encoded = Uri.EscapeDataString(target);
+ string apiPrefix = Program.GetAppConfig("CitiApiPrefix");
+ string url = $"{apiPrefix}asset/ip/report?ip={encoded}&full=true";
+
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Headers.Add("x-api-key", apiKey);
+ request.Headers.Add("User-Agent", context.Request.UserAgent);
+
+ HttpResponseMessage response = await _httpClient.SendAsync(request);
+ string content = await response.Content.ReadAsStringAsync();
+
+ context.Response.StatusCode = (int)response.StatusCode;
+ Server.ServeResource(context, content, "application/json", (int)response.StatusCode);
+ }
+ catch (Exception ex)
+ {
+ Server.ServeResource(context, $"{ex.Message}", "application/xml", 500);
+ }
+ }
+ }
+}
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
index 9c69d5f..7f2c096 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
@@ -69,6 +69,7 @@
+
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
index 8c641cf..a9f7f58 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
@@ -15,6 +15,8 @@
+
+
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html
index 42425aa..4691acd 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html
@@ -119,7 +119,8 @@
function RibbonMenu({
onOpenFileClick, onSaveFileClick, onCopliotClick, onAzureAiClick,
- onSavePromptClick, onLoadPromptClick, onQueryWhoisClick, onQueryDnsClick
+ onSavePromptClick, onLoadPromptClick, onQueryWhoisClick, onQueryDnsClick,
+ onQueryCitiClick
}) {
const fileButtons = [
{
@@ -145,7 +146,8 @@
const networkToolsButtons = [
{ id: 'btnWhois', icon: 'mif-earth', caption: 'Whois', onClick: onQueryWhoisClick },
- { id: 'btnDnsQuery', icon: 'mif-earth', caption: 'DNS', onClick: onQueryDnsClick }
+ { id: 'btnQueryDns', icon: 'mif-earth', caption: 'DNS', onClick: onQueryDnsClick },
+ { id: 'btnQueryCiti', icon: 'mif-user-secret', caption: 'IP', onClick: onQueryCitiClick }
];
return _e(
@@ -465,6 +467,11 @@
pushPromptMessage("user", promptMessage);
const apiKey = settingsRef.current.AzureAiServiceApiKey;
+ if (!apiKey || apiKey.trim() == '') {
+ alert("Azure AI API key is not set.");
+ return;
+ }
+
const url = `${settingsRef.current.AzureAiServicePrefix}models/chat/completions?api-version=${settingsRef.current.AzureAiServiceApiVersion}`;
const data = {
@@ -537,7 +544,7 @@
axios.get(`${serverPrefix}whois/${hostname}`).then(response => {
const responseText = DOMPurify.sanitize(response.data, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] });
- appendTextToEditor(`/*\n${responseText}\n*/`);
+ appendTextToEditor(`/*\nHostname:${hostname}\n\n${responseText}\n*/`);
pushPromptMessage("system", responseText);
}).catch(error => {
console.error(error);
@@ -553,13 +560,78 @@
axios.get(`${serverPrefix}dns-query/${hostname}`).then(response => {
const responseText = response.data;
- appendTextToEditor(`/*\n${responseText}\n*/`);
+ appendTextToEditor(`/*\nHostname:${hostname}\n\n${responseText}\n*/`);
pushPromptMessage("system", responseText);
}).catch(error => {
console.error(error);
});
};
+ const queryCiti = () => {
+ const hostname = prompt("Enter IP address:", '');
+ if (!hostname || hostname.trim() === '') {
+ appendTextToEditor("\n// IP address is required.");
+ return;
+ }
+
+ const apiKey = settingsRef.current.CitiApiKey;
+ if (!apiKey || apiKey.trim() === '') {
+ appendTextToEditor("\n// Criminal IP API key is not set.");
+ return;
+ }
+
+ const apiPrefix = settingsRef.current.CitiApiPrefix;
+ const ip = encodeURIComponent(hostname.trim());
+
+ axios.get(`${serverPrefix}citi-query/${hostname}`).then(response => {
+ const parser = new XMLParser();
+ const result = parser.parse(response.data);
+ const data = JSON.parse(result.json);
+ if (!data) {
+ appendTextToEditor("\n// No data returned from Criminal IP.");
+ return;
+ }
+
+ const lines = [];
+ lines.push(`/*\nCriminal IP Report: ${hostname}\n`);
+
+ // network port data
+ lines.push(`## Network ports:`);
+ if (data.port.data.length == 0) {
+ lines.push(`* No open ports found.`);
+ } else {
+ data.port.data.forEach(x => {
+ lines.push(`### ${x.open_port_no}/${x.socket}`);
+ lines.push(`* Application: ${x.app_name} ${x.app_version}`);
+ lines.push(`* Discovered hostnames: ${x.dns_names}`);
+ lines.push(`* Confirmed Time: ${x.confirmed_time}`);
+ });
+ }
+
+ // vulnerability data
+ lines.push(`## Vulnerabilities:`);
+ if (data.vulnerability.data.length == 0) {
+ lines.push(`* No vulnerabilities found.`);
+ } else {
+ data.vulnerability.data.forEach(x => {
+ lines.push(`### ${x.cve_id}`);
+ lines.push(`* ${x.cve_description}`);
+ lines.push(`* CVSSV2 Score: ${x.cvssv2_score}`);
+ lines.push(`* CVSSV3 Score: ${x.cvssv3_score}`);
+ });
+ }
+
+ lines.push(`*/\n`);
+ const report = lines.join('\n');
+
+ appendTextToEditor(report);
+ pushPromptMessage("system", report);
+ }).catch(error => {
+ console.error(error);
+ appendTextToEditor(`\n// Failed to query Criminal IP: ${error.message}`);
+ });
+ };
+
React.useEffect(() => {
window.addEventListener('resize', () => {
resizeEditor();
@@ -578,7 +650,8 @@
onSavePromptClick: savePromptMessages,
onLoadPromptClick: loadPromptMessages,
onQueryWhoisClick: queryWhois,
- onQueryDnsClick: queryDns
+ onQueryDnsClick: queryDns,
+ onQueryCitiClick: queryCiti
}),
_e('div', { id: 'container' },
_e(Editor, { editorRef }),