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 }),