diff --git a/Shodan.Net.sln b/Shodan.Net.sln new file mode 100644 index 0000000..6da622d --- /dev/null +++ b/Shodan.Net.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{060AE71C-AE05-4F14-8970-84A1AE49A562}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{18A95621-2E1B-48F0-9D38-D7B3513F31D3}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Shodan.Net", "src\Shodan.Net\Shodan.Net.xproj", "{2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Shodan.Net.UnitTests", "src\Shodan.Net.UnitTests\Shodan.Net.UnitTests.xproj", "{F83A8130-97B5-4AA2-810C-1D16A88799BD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Release|Any CPU.Build.0 = Release|Any CPU + {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8} = {060AE71C-AE05-4F14-8970-84A1AE49A562} + {F83A8130-97B5-4AA2-810C-1D16A88799BD} = {060AE71C-AE05-4F14-8970-84A1AE49A562} + EndGlobalSection +EndGlobal diff --git a/global.json b/global.json new file mode 100644 index 0000000..b51e28b --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-preview1-002702" + } +} diff --git a/src/Shodan.Net.UnitTests/Class1.cs b/src/Shodan.Net.UnitTests/Class1.cs new file mode 100644 index 0000000..8bed8bd --- /dev/null +++ b/src/Shodan.Net.UnitTests/Class1.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Shodan.Net.UnitTests +{ + // This project can output the Class library as a NuGet Package. + // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". + public class Class1 + { + [Fact] + public async Task privateGetsPorts() + + { + var client = new ShodanClient("9F0mxmNSaHbe0mYmefwoCZrChT2h0KzC"); + var ports = await client.GetPortsAsync(); + } + } +} \ No newline at end of file diff --git a/src/Shodan.Net.UnitTests/Properties/AssemblyInfo.cs b/src/Shodan.Net.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..061ef98 --- /dev/null +++ b/src/Shodan.Net.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Shodan.Net.UnitTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f83a8130-97b5-4aa2-810c-1d16a88799bd")] diff --git a/src/Shodan.Net.UnitTests/Shodan.Net.UnitTests.xproj b/src/Shodan.Net.UnitTests/Shodan.Net.UnitTests.xproj new file mode 100644 index 0000000..3674896 --- /dev/null +++ b/src/Shodan.Net.UnitTests/Shodan.Net.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + f83a8130-97b5-4aa2-810c-1d16a88799bd + Shodan.Net.UnitTests + .\obj + .\bin\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/src/Shodan.Net.UnitTests/project.json b/src/Shodan.Net.UnitTests/project.json new file mode 100644 index 0000000..55d51cb --- /dev/null +++ b/src/Shodan.Net.UnitTests/project.json @@ -0,0 +1,25 @@ +{ + "version": "1.0.0-*", + "testRunner": "xunit", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-rc2-build10015", + "Shodan.Net": "1.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + } + }, + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Class1.cs b/src/Shodan.Net/Class1.cs new file mode 100644 index 0000000..63e0f75 --- /dev/null +++ b/src/Shodan.Net/Class1.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + // This project can output the Class library as a NuGet Package. + // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". + public class Class1 + { + public Class1() + { + } + } +} diff --git a/src/Shodan.Net/IShodanAsyncClient.cs b/src/Shodan.Net/IShodanAsyncClient.cs new file mode 100644 index 0000000..ba51b87 --- /dev/null +++ b/src/Shodan.Net/IShodanAsyncClient.cs @@ -0,0 +1,124 @@ +using Shodan.Net.Models; +using Shodan.Net.Models.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + internal interface IShodanAsyncClient + { + /// + /// Returns all services that have been found on the given host IP. + /// + /// Host IP address + /// True if all historical banners should be returned (default: False) + /// True to only return the list of ports and the general host information, no banners. (default: False) + /// + Task GetHostAsync(string Ip, bool history = false, bool minify = false); + + /// + /// This method returns a list of port numbers that the crawlers are looking for. + /// + /// + Task> GetPortsAsync(); + + /// + /// This method returns an object containing all the protocols that can be used when launching an Internet scan. + /// + /// + Task> GetProtocolsAsync(); + + /// + /// Use this method to request Shodan to crawl a network + /// Requirements: This method uses API scan credits: 1 IP consumes 1 scan credit. You must have a paid API plan (either one-time payment or subscription) in order to use this method + /// + /// + Task RequstScanAsync(string ips); + + /// + /// + /// + /// The port that Shodan should crawl the Internet for. + /// The name of the protocol that should be used to interrogate the port. See /shodan/protocols for a list of supported protocols. + /// + Task RequestInternetPortScanAsync(int port, string protocol); + + /// + /// Check the progress of a previously submitted scan request + /// + /// the unique scan ID that was returned by /shodan/scan + /// + Task GetScanStatusAsync(string id); + + /// + /// This method returns an object containing all the services that the Shodan crawlers look at. It can also be used as a quick and practical way to resolve a port number to the name of a service + /// + /// + Task> GetServicesAsync(); + + /// + /// Use this method to obtain a list of search queries that users have saved in Shodan. + /// + /// Page number to iterate over results; each page contains 10 items + /// Sort the list based on a property. Possible values are: votes, timestamp + /// Whether to sort the list in ascending or descending order. Possible values are: asc, desc + /// + Task GetQueriesAsync(int? page = null, SortOptions? options = null, OrderOption? order = null); + + /// + /// Use this method to search the directory of search queries that users have saved in Shodan. + /// + /// What to search for in the directory of saved search queries. + /// Page number to iterate over results; each page contains 10 items + /// + Task SearchQueriesAsync(string query, int? page = null); + + /// + /// Use this method to obtain a list of popular tags for the saved search queries in Shodan. + /// + /// The number of tags to return + /// + Task GetTagsAsync(int size = 10); + + /// + /// Returns information about the Shodan account linked to this API key. + /// + /// + Task GetProfileAsync(); + + /// + /// Look up the IP address for the provided list of hostnames. + /// + /// Comma-separated list of hostnames; example "google.com,bing.com" + /// + Task> DnsLookupAsync(string hostnames); + + /// + /// Look up the hostnames that have been defined for the given list of IP addresses + /// + /// Comma-separated list of IP addresses; example "74.125.227.230,204.79.197.200" + /// + Task>> ReverseLookupAsync(string ips); + + /// + /// Get your current IP address as seen from the Internet. + /// + /// + Task GetMyIpAsync(); + + /// + /// Returns information about the API plan belonging to the given API key. + /// + /// + Task GetApiStatusAsync(); + + /// + /// Calculates a honeypot probability score rangin from 0 (not a honeypot) to 1.0 (is a honeypot). + /// + /// + /// + Task Experimental_GetHoneyPotScoreAsync(string ip); + } +} \ No newline at end of file diff --git a/src/Shodan.Net/IShodanClient.cs b/src/Shodan.Net/IShodanClient.cs new file mode 100644 index 0000000..e522897 --- /dev/null +++ b/src/Shodan.Net/IShodanClient.cs @@ -0,0 +1,124 @@ +using Shodan.Net.Models; +using Shodan.Net.Models.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + public interface IShodanClient + { + /// + /// Returns all services that have been found on the given host IP. + /// + /// Host IP address + /// True if all historical banners should be returned (default: False) + /// True to only return the list of ports and the general host information, no banners. (default: False) + /// + Host GetHost(string Ip, bool history = false, bool minify = false); + + /// + /// This method returns a list of port numbers that the crawlers are looking for. + /// + /// + List GetPorts(); + + /// + /// This method returns an object containing all the protocols that can be used when launching an Internet scan. + /// + /// + Dictionary GetProtocols(); + + /// + /// Use this method to request Shodan to crawl a network + /// Requirements: This method uses API scan credits: 1 IP consumes 1 scan credit. You must have a paid API plan (either one-time payment or subscription) in order to use this method + /// + /// + ScanResult RequstScan(string ips); + + /// + /// + /// + /// The port that Shodan should crawl the Internet for. + /// The name of the protocol that should be used to interrogate the port. See /shodan/protocols for a list of supported protocols. + /// + ScanPortResult RequestPortScan(int port, string protocol); + + /// + /// Check the progress of a previously submitted scan request + /// + /// the unique scan ID that was returned by /shodan/scan + /// + ScanStatus GetScanStatus(string id); + + /// + /// This method returns an object containing all the services that the Shodan crawlers look at. It can also be used as a quick and practical way to resolve a port number to the name of a service + /// + /// + Dictionary GetServices(); + + /// + /// Use this method to obtain a list of search queries that users have saved in Shodan. + /// + /// Page number to iterate over results; each page contains 10 items + /// Sort the list based on a property. Possible values are: votes, timestamp + /// Whether to sort the list in ascending or descending order. Possible values are: asc, desc + /// + SearchQueries GetQueries(int? page = null, SortOptions? options = null, OrderOption? order = null); + + /// + /// Use this method to search the directory of search queries that users have saved in Shodan. + /// + /// What to search for in the directory of saved search queries. + /// Page number to iterate over results; each page contains 10 items + /// + SearchQueries GetQuerySearches(string query, int? page = null); + + /// + /// Use this method to obtain a list of popular tags for the saved search queries in Shodan. + /// + /// The number of tags to return + /// + TagResult GetTags(int size = 10); + + /// + /// Returns information about the Shodan account linked to this API key. + /// + /// + Profile GetProfile(); + + /// + /// Look up the IP address for the provided list of hostnames. + /// + /// Comma-separated list of hostnames; example "google.com,bing.com" + /// + Dictionary DnsLookup(string hostnames); + + /// + /// Look up the hostnames that have been defined for the given list of IP addresses + /// + /// Comma-separated list of IP addresses; example "74.125.227.230,204.79.197.200" + /// + Dictionary> ReverseLookup(string ips); + + /// + /// Get your current IP address as seen from the Internet. + /// + /// + string GetMyIp(); + + /// + /// Returns information about the API plan belonging to the given API key. + /// + /// + ApiStatus GetApiStatus(); + + /// + /// Calculates a honeypot probability score rangin from 0 (not a honeypot) to 1.0 (is a honeypot). + /// + /// + /// + double Experimental_GetHoneyPotScore(string ip); + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/ApiStatus.cs b/src/Shodan.Net/Models/ApiStatus.cs new file mode 100644 index 0000000..a9f0b8d --- /dev/null +++ b/src/Shodan.Net/Models/ApiStatus.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class ApiStatus + { + [DataMember(Name = "query_credits")] + public int QueryCredits { get; set; } + + [DataMember(Name = "scan_credits")] + public int ScanCredits { get; set; } + + [DataMember(Name = "telnet")] + public bool Telnet { get; set; } + + [DataMember(Name = "plan")] + public string Plan { get; set; } + + [DataMember(Name = "https")] + public bool Https { get; set; } + + [DataMember(Name = "unlocked")] + public bool Unlocked { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/Banner.cs b/src/Shodan.Net/Models/Banner.cs new file mode 100644 index 0000000..76af578 --- /dev/null +++ b/src/Shodan.Net/Models/Banner.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class Banner + { + /// + /// The timestamp for when the banner was fetched from the device in the UTC timezone. + /// + [DataMember(Name = "timestamp")] + public DateTime Timestamp { get; set; } + + /// + /// Either "udp" or "tcp" to indicate which IP transport protocol was used to fetch the information + /// + [DataMember(Name = "transport")] + public string Transport { get; set; } + + /// + /// An array of strings containing all of the hostnames that have been assigned to the IP address for this device. + /// + [DataMember(Name = "hostnames")] + public IList Hostnames { get; set; } + + /// + /// The name of the organization that is assigned the IP space for this device. + /// + [DataMember(Name = "org")] + public string Org { get; set; } + + [DataMember(Name = "guid")] + public string Guid { get; set; } + + /// + /// Contains the banner information for the service. + /// + [DataMember(Name = "data")] + public string Data { get; set; } + + /// + /// The port number that the service is operating on + /// + [DataMember(Name = "port")] + public int Port { get; set; } + + /// + /// The ISP that is providing the organization with the IP space for this device. Consider this the "parent" of the organization in terms of IP ownership. + /// + [DataMember(Name = "isp")] + public string Isp { get; set; } + + /// + /// The autonomous system number (ex. "AS4837" + /// + [DataMember(Name = "asn")] + public string Asn { get; set; } + + [DataMember(Name = "location")] + public Location Location { get; set; } + + /// + /// The IP address of the host as an integer + /// + [DataMember(Name = "ip")] + public int? Ip { get; set; } + + /// + /// The IPv6 address of the host as a string. If this is present then the "ip" and "ip_str" fields wont be. + /// + [DataMember(Name = "ip")] + public string Ipv6 { get; set; } + + /// + /// ] An array of strings containing the top-level domains for the hostnames of the device. This is a utility property in case you want to filter by TLD instead of subdomain. It is smart enough to handle global TLDs with several dots in the domain (ex. "co.uk") + /// + [DataMember(Name = "domains")] + public IList Domains { get; set; } + + /// + /// The IP address of the host as a string + /// + [DataMember(Name = "ip_str")] + public string IpStr { get; set; } + + /// + /// The operating system that powers the device. + /// + [DataMember(Name = "os")] + public object Os { get; set; } + + /// + /// Contains experimental and supplemental data for the service. This can include the SSL certificate, robots.txt and other raw information that hasn't yet been formalized into the Banner Specification. + /// + [DataMember(Name = "opts", IsRequired = false)] + public dynamic Opts { get; set; } + + #region Optional Properties + + /// + /// The number of minutes that the device has been online. + /// + [DataMember(Name = "uptime", IsRequired = false)] + public int? Uptime { get; set; } + + [DataMember(Name = "link", IsRequired = false)] + public string Link { get; set; } + + [DataMember(Name = "title", IsRequired = false)] + public string Title { get; set; } + + [DataMember(Name = "html", IsRequired = false)] + public string Html { get; set; } + + [DataMember(Name = "product", IsRequired = false)] + public string Product { get; set; } + + [DataMember(Name = "version", IsRequired = false)] + public string Version { get; set; } + + [DataMember(Name = "devicetype", IsRequired = false)] + public string DeviceType { get; set; } + + [DataMember(Name = "info", IsRequired = false)] + public string Info { get; set; } + + [DataMember(Name = "cpe", IsRequired = false)] + public string Cpe { get; set; } + + [DataMember(Name = "ssl", IsRequired = false)] + public SslProperties Ssl { get; set; } + + #endregion Optional Properties + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/Host.cs b/src/Shodan.Net/Models/Host.cs new file mode 100644 index 0000000..0752d33 --- /dev/null +++ b/src/Shodan.Net/Models/Host.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class Host + { + [DataMember(Name = "region_code")] + public string RegionCode { get; set; } + + [DataMember(Name = "ip")] + public string Ip { get; set; } + + [DataMember(Name = "area_code")] + public string AreaCode { get; set; } + + [DataMember(Name = "country_names")] + public string CountryName { get; set; } + + [DataMember(Name = "hostnames")] + public List Hostnames { get; set; } + + [DataMember(Name = "postal_code")] + public string PostalCode { get; set; } + + [DataMember(Name = "dma_code")] + public string DmaCode { get; set; } + + [DataMember(Name = "country_code")] + public string CountryCode { get; set; } + + [DataMember(Name = "data")] + public List Data { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/Location.cs b/src/Shodan.Net/Models/Location.cs new file mode 100644 index 0000000..f35479c --- /dev/null +++ b/src/Shodan.Net/Models/Location.cs @@ -0,0 +1,78 @@ +using System.Runtime.Serialization; + +namespace Shodan.Net.Models +{ + [DataContract] + public class Location + { + /// + /// An object containing all of the location information for the device. + /// + public Location() + { + } + + /// + /// The 3-letter country code for the device location. + /// + + [DataMember(Name = "country_code3")] + public string CountryCode3 { get; set; } + + /// + /// The name of the city where the device is located. + /// + [DataMember(Name = "city")] + public string City { get; set; } + + /// + /// The postal code for the device's location. + /// + [DataMember(Name = "postal_code")] + public string PostalCode { get; set; } + + /// + /// The longitude for the geolocation of the device. + /// + [DataMember(Name = "longitude")] + public double Longitude { get; set; } + + /// + /// The 2-letter country code for the device location. + /// + + [DataMember(Name = "country_code")] + public string CountryCode { get; set; } + + /// + /// The latitude for the geolocation of the device + /// + + [DataMember(Name = "latitude")] + public double Latitude { get; set; } + + /// + /// The name of the country where the device is located. + /// + [DataMember(Name = "country_name")] + public string CountryName { get; set; } + + /// + /// The area code for the device's location. Only available for the US. + /// + [DataMember(Name = "area_code")] + public int AreaCode { get; set; } + + /// + /// The designated market area code for the area where the device is located. Only available for the US. + /// + [DataMember(Name = "dma_code")] + public int? DmaCode { get; set; } + + /// + /// The name of the region where the device is located. + /// + [DataMember(Name = "region_code")] + public string RegionCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/Options/OrderOption.cs b/src/Shodan.Net/Models/Options/OrderOption.cs new file mode 100644 index 0000000..8ac5eca --- /dev/null +++ b/src/Shodan.Net/Models/Options/OrderOption.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net.Models.Options +{ + public enum OrderOption + { + asc, + desc + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/Options/RequestType.cs b/src/Shodan.Net/Models/Options/RequestType.cs new file mode 100644 index 0000000..b955516 --- /dev/null +++ b/src/Shodan.Net/Models/Options/RequestType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net.Models.Options +{ + public enum RequestType + { + GET = 0, + PUT = 1, + POST = 2, + DELETE = 4 + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/Options/SortOptions.cs b/src/Shodan.Net/Models/Options/SortOptions.cs new file mode 100644 index 0000000..1cc059f --- /dev/null +++ b/src/Shodan.Net/Models/Options/SortOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net.Models.Options +{ + public enum SortOptions + { + votes, + timestamp + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/Profile.cs b/src/Shodan.Net/Models/Profile.cs new file mode 100644 index 0000000..4785bea --- /dev/null +++ b/src/Shodan.Net/Models/Profile.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class Profile + { + [DataMember(Name = "member")] + public bool Member { get; set; } + + [DataMember(Name = "credits")] + public int Credits { get; set; } + + [DataMember(Name = "display_name")] + public string DisplayName { get; set; } + + [DataMember(Name = "created")] + public DateTime Created { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/ScanPortResult.cs b/src/Shodan.Net/Models/ScanPortResult.cs new file mode 100644 index 0000000..c88d0b8 --- /dev/null +++ b/src/Shodan.Net/Models/ScanPortResult.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class ScanPortResult + { + [DataMember(Name = "id")] + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/ScanResult.cs b/src/Shodan.Net/Models/ScanResult.cs new file mode 100644 index 0000000..90dbca7 --- /dev/null +++ b/src/Shodan.Net/Models/ScanResult.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class ScanResult + { + [DataMember(Name = "id")] + public string Id { get; set; } + + [DataMember(Name = "count")] + public int Count { get; set; } + + [DataMember(Name = "credits_left")] + public int CreditsLeft { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/ScanStatus.cs b/src/Shodan.Net/Models/ScanStatus.cs new file mode 100644 index 0000000..c9ee9c7 --- /dev/null +++ b/src/Shodan.Net/Models/ScanStatus.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class ScanStatus + { + [DataMember(Name = "id")] + public string Id { get; set; } + + [DataMember(Name = "count")] + public int Count { get; set; } + + [DataMember(Name = "status")] + public StatusEnum? Status { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/SearchQueries.cs b/src/Shodan.Net/Models/SearchQueries.cs new file mode 100644 index 0000000..5bd2329 --- /dev/null +++ b/src/Shodan.Net/Models/SearchQueries.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class Match + { + [DataMember(Name = "votes")] + public int Votes { get; set; } + + [DataMember(Name = "description")] + public string Description { get; set; } + + [DataMember(Name = "title")] + public string Title { get; set; } + + [DataMember(Name = "timestamp")] + public DateTime Timestamp { get; set; } + + [DataMember(Name = "tags")] + public IList Tags { get; set; } + + [DataMember(Name = "query")] + public string Query { get; set; } + } + + [DataContract] + public class SearchQueries + { + [DataMember(Name = "total")] + public int Total { get; set; } + + [DataMember(Name = "matches")] + public IList Matches { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/SslProperties.cs b/src/Shodan.Net/Models/SslProperties.cs new file mode 100644 index 0000000..3873fba --- /dev/null +++ b/src/Shodan.Net/Models/SslProperties.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class SslProperties + { + /// + /// The parsed certificate properties that includes information such as when it was issued, the SSL extensions, the issuer, subject etc. + /// + [DataMember(Name = "cert")] + public dynamic Cert { get; set; } + + /// + /// Preferred cipher for the SSL connection + /// + [DataMember(Name = "ciper")] + public dynamic Ciper { get; set; } + + /// + /// An array of certificates, where each string is a PEM-encoded SSL certificate. This includes the user SSL certificate up to its root certificate. + /// + [DataMember(Name = "chain")] + public IList Chain { get; set; } + + /// + /// The Diffie-Hellman parameters if available: "prime", "public_key", "bits", "generator" and an optional "fingerprint" if we know which program generated these parameters. + /// + [DataMember(Name = "dhparams")] + public dynamic DhParams { get; set; } + + /// + /// A list of SSL versions that are supported by the server. If a version isnt supported the value is prefixed with a "-". Example: ["TLSv1", "-SSLv2"] means that the server supports TLSv1 but doesnt support SSLv2. + /// + [DataMember(Name = "versions")] + public IList Versions { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/StatusEnum.cs b/src/Shodan.Net/Models/StatusEnum.cs new file mode 100644 index 0000000..c2d7ae1 --- /dev/null +++ b/src/Shodan.Net/Models/StatusEnum.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + public enum StatusEnum + { + SUBMITTING, + QUEUE, + PROCESSING, + DONE + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Models/TagResult.cs b/src/Shodan.Net/Models/TagResult.cs new file mode 100644 index 0000000..c3a0b0c --- /dev/null +++ b/src/Shodan.Net/Models/TagResult.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Shodan.Net.Models +{ + [DataContract] + public class TagResult + { + [DataMember(Name = "total")] + public int Total { get; set; } + + [DataMember(Name = "matches")] + public IList Matches { get; set; } + + [DataContract] + public class Match + { + [DataMember(Name = "value")] + public string Value { get; set; } + + [DataMember(Name = "count")] + public int Count { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Properties/AssemblyInfo.cs b/src/Shodan.Net/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..36f7aac --- /dev/null +++ b/src/Shodan.Net/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Shodan.Net")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2ac1566f-7c77-499e-b9ae-1f029bd7f7e8")] diff --git a/src/Shodan.Net/QueryGenerator.cs b/src/Shodan.Net/QueryGenerator.cs new file mode 100644 index 0000000..01cf26b --- /dev/null +++ b/src/Shodan.Net/QueryGenerator.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + public class QueryGenerator + { + internal Dictionary queryData { get; set; } + private HashSet CalledMethods = new HashSet(); + + public QueryGenerator Before(DateTime time) + { + queryData.Add("before", time.ToString("dd/MM/yyyy")); + return this; + } + + /// + /// Only show results that were collected after the given date + /// + /// + /// + public QueryGenerator After(DateTime time) + { + queryData.Add("after", time.ToString("dd/MM/yyyy")); + return this; + } + + /// + /// The Autonomous System Number that identifies the network the device is on. + /// + /// + /// + public QueryGenerator WithAsn(string asn) + { + queryData.Add("asn", asn); + return this; + } + + public QueryGenerator InCity(string city) + { + queryData.Add("city", city); + return this; + } + + public QueryGenerator InCountry(string country) + { + queryData.Add("country", country); + return this; + } + + /// + /// If "true" only show results that have a screenshot available. + /// + /// + /// + public QueryGenerator HasScreenshot(bool hasScreenshot = true) + { + queryData.Add("has_screenshot", hasScreenshot.ToString()); + return this; + } + + public QueryGenerator WithHostname(string hostname) + { + queryData.Add("hostname", hostname); + return this; + } + + public QueryGenerator WithHtml(string html) + { + queryData.Add("html", html); + return this; + } + + public QueryGenerator WithIsp(string isp) + { + queryData.Add("isp", isp); + return this; + } + + public QueryGenerator WithNet(string net) + { + queryData.Add("net", net); + return this; + } + + public QueryGenerator WithOrg(string org) + { + queryData.Add("org", org); + return this; + } + + public QueryGenerator WithOs(string os) + { + queryData.Add("os", os); + return this; + } + + public QueryGenerator Withport(string port) + { + queryData.Add("port", port); + return this; + } + + public QueryGenerator With_postal(string postal) + { + queryData.Add("postal", postal); + return this; + } + + public QueryGenerator With_product(string product) + { + queryData.Add("product", product); + return this; + } + + public QueryGenerator With_state(string state) + { + queryData.Add("state", state); + return this; + } + + public QueryGenerator With_title(string title) + { + queryData.Add("title", title); + return this; + } + + public QueryGenerator With_version(string version) + { + queryData.Add("version", version); + return this; + } + + public QueryGenerator With_Bitcoinip(string bitcoinip) + { + queryData.Add("bitcoin.ip", bitcoinip); + return this; + } + + public QueryGenerator With_Bitcoinip_count(string bitcoinip_count) + { + queryData.Add("bitcoin.ip_count", bitcoinip_count); + return this; + } + + public QueryGenerator WithBitcoinport(string bitcoinport) + { + queryData.Add("bitcoin.port", bitcoinport); + return this; + } + + public QueryGenerator WithBitcoinversion(string bitcoinversion) + { + queryData.Add("bitcoin.version", bitcoinversion); + return this; + } + + public QueryGenerator WithNtpip(string ntpip) + { + queryData.Add("ntp.ip", ntpip); + return this; + } + + public QueryGenerator WithNtpip_count(string ntpip_count) + { + queryData.Add("ntp.ip_count", ntpip_count); + return this; + } + + public QueryGenerator WithNtpmore(string ntpmore) + { + queryData.Add("ntp.more", ntpmore); + return this; + } + + public QueryGenerator WithNtpport(string ntpport) + { + queryData.Add("ntp.port", ntpport); + return this; + } + + public SearchQuery Generate(string searchText) + { + var sb = new StringBuilder(searchText); + sb.Append(" "); + foreach(var item in queryData) + { + sb.Append($"{item.Key}:{item.Value}"); + } + return new SearchQuery(sb.ToString()); + } + + private void EnsureMethodNotCalled(string methodName) + { + if(CalledMethods.Contains(methodName)) + { + throw new ShodanException($"{methodName} cannot be called twice"); + } + CalledMethods.Add(methodName); + } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/SearchQuery.cs b/src/Shodan.Net/SearchQuery.cs new file mode 100644 index 0000000..7909d2e --- /dev/null +++ b/src/Shodan.Net/SearchQuery.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + public class SearchQuery + { + internal SearchQuery(string builtQuery) + { + this.Query = builtQuery; + } + + internal string Query { get; set; } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/SearchQueryGenerator.cs b/src/Shodan.Net/SearchQueryGenerator.cs new file mode 100644 index 0000000..461dd46 --- /dev/null +++ b/src/Shodan.Net/SearchQueryGenerator.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + public class SearchQueryGenerator + { + public QueryGenerator WithQuery() => new QueryGenerator(); + } +} \ No newline at end of file diff --git a/src/Shodan.Net/Shodan.Net.xproj b/src/Shodan.Net/Shodan.Net.xproj new file mode 100644 index 0000000..b0fa893 --- /dev/null +++ b/src/Shodan.Net/Shodan.Net.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 2ac1566f-7c77-499e-b9ae-1f029bd7f7e8 + Shodan.Net + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/Shodan.Net/ShodanClient.cs b/src/Shodan.Net/ShodanClient.cs new file mode 100644 index 0000000..5053116 --- /dev/null +++ b/src/Shodan.Net/ShodanClient.cs @@ -0,0 +1,326 @@ +using Newtonsoft.Json; +using Shodan.Net.Models; +using Shodan.Net.Models.Options; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + public class ShodanClient : IShodanAsyncClient + { + private readonly string apikey; + private const string BasePath = "https://api.shodan.io"; + + //todo error handle!!! + //todo: + /* + /shodan/host/count + /shodan/host/search + /shodan/host/search/tokens + */ + + public ShodanClient(string apikey) + { + if(string.IsNullOrWhiteSpace(apikey)) + { + throw new ArgumentNullException(nameof(apikey)); + } + this.apikey = apikey; + } + + /// + /// Look up the IP address for the provided list of hostnames. + /// + /// Comma-separated list of hostnames; example "google.com,bing.com" + /// + public Task> DnsLookupAsync(string hostnames) + { + if(string.IsNullOrWhiteSpace(hostnames)) + { + throw new ArgumentNullException(hostnames); + } + var url = new Uri($"{BasePath}/dns/resolve?hostnames={hostnames}&key={this.apikey}"); + return MakeRequestAsync>(url); + } + + /// + /// Calculates a honeypot probability score ranging from 0 (not a honeypot) to 1.0 (is a honeypot). + /// + /// + /// + public async Task Experimental_GetHoneyPotScoreAsync(string ip) + { + if(string.IsNullOrWhiteSpace(ip)) + { + throw new ArgumentNullException(nameof(ip)); + } + var url = new Uri($"{BasePath}/labs/honeyscore/{ip}?key={apikey}"); + var result = await MakeRequestAsync(url); + double resultParsed; + if(!double.TryParse(result, out resultParsed)) + { + throw new ShodanException($"honeypot score returned with {result} failed to parse to double"); + } + return resultParsed; + } + + /// + /// Returns information about the API plan belonging to the given API key. + /// + /// + public Task GetApiStatusAsync() + { + var url = new Uri($"{BasePath}/api-info?key={apikey}"); + return MakeRequestAsync(url); + } + + /// + /// Returns all services that have been found on the given host IP. + /// + /// Host IP address + /// True if all historical banners should be returned (default: False) + /// True to only return the list of ports and the general host information, no banners. (default: False) + /// + public Task GetHostAsync(string Ip, bool history = false, bool minify = false) + { + if(string.IsNullOrWhiteSpace(Ip)) + { + throw new ArgumentNullException(nameof(Ip)); + } + var builder = new UriBuilder($"{BasePath}/shodan/host/{Ip}") + { + Query = $"key={this.apikey}&history={history.ToString()}&minify={minify.ToString()}" + }; + + return MakeRequestAsync(builder.Uri); + } + + /// + /// Get your current IP address as seen from the Internet. + /// + /// + public Task GetMyIpAsync() + { + var url = new Uri($"{BasePath}/tools/myip?key={this.apikey}"); + return MakeRequestAsync(url); + } + + /// + /// This method returns a list of port numbers that the crawlers are looking for. + /// + /// + public Task> GetPortsAsync() + { + var builder = new Uri($"{BasePath}/shodan/ports?key={this.apikey}"); + return MakeRequestAsync>(builder); + } + + private async static Task MakeRequestAsync(Uri url, HttpContent content = null, RequestType requstType = RequestType.GET) + where T : class + { + if(requstType != RequestType.GET && content == null) + { + throw new ShodanException($"Request type {requstType} requires content"); + } + if(requstType == RequestType.DELETE || requstType == RequestType.PUT) + { + throw new NotImplementedException("Put and Delete requests have not been implemented properly"); + } + using(var client = new HttpClient()) + { + HttpResponseMessage connection = null; + if(requstType == RequestType.GET) + { + connection = await client.GetAsync(url); + } + else if(requstType == RequestType.POST) + { + connection = await client.PostAsync(url, content); + } + + var statusCode = (int)connection.StatusCode; + if(statusCode != 200 && statusCode != 201 && statusCode == 202) + { + //todo error handle + return null; + } + var readResult = await connection.Content.ReadAsStringAsync(); + if(typeof(T) == typeof(string)) + { + return readResult as T; + } + return JsonConvert.DeserializeObject(readResult); + } + } + + /// + /// Returns information about the Shodan account linked to this API key. + /// + /// + public Task GetProfileAsync() + { + var url = new Uri($"{BasePath}/account/profile?key={apikey}"); + return MakeRequestAsync(url); + } + + /// + /// This method returns an object containing all the protocols that can be used when launching an Internet scan. + /// + /// + public Task> GetProtocolsAsync() + { + var url = new Uri($"{BasePath}/shodan/protocols?key={this.apikey}"); + return MakeRequestAsync>(url); + } + + /// + /// Use this method to obtain a list of search queries that users have saved in Shodan. + /// + /// Page number to iterate over results; each page contains 10 items + /// Sort the list based on a property. Possible values are: votes, timestamp + /// Whether to sort the list in ascending or descending order. Possible values are: asc, desc + /// + public Task GetQueriesAsync(int? page = null, SortOptions? sort = null, OrderOption? order = null) + { + var url = new UriBuilder($"{BasePath}/shodan/query") + { + Query = $"key={apikey}" + }; + if(sort.HasValue) + { + var sortName = Enum.GetName(typeof(SortOptions), sort.Value); + url.Query = $"{url.Query}&sort={sortName}"; + } + if(order.HasValue) + { + var orderName = Enum.GetName(typeof(OrderOption), order.Value); + url.Query = $"{url.Query}&order={orderName}"; + } + return MakeRequestAsync(url.Uri); + } + + /// + /// Use this method to search the directory of search queries that users have saved in Shodan. + /// + /// What to search for in the directory of saved search queries. + /// Page number to iterate over results; each page contains 10 items + /// + public Task SearchQueriesAsync(string query, int? page = null) + { + if(string.IsNullOrWhiteSpace(query)) + { + throw new ArgumentNullException(query); + } + var url = new UriBuilder($"{BasePath}/shodan/query/search") + { + Query = $"key={apikey}&query={query}" + }; + if(page != null) + { + url.Query = $"{url.Query}&page={page}"; + } + return MakeRequestAsync(url.Uri); + } + + /// + /// Check the progress of a previously submitted scan request + /// + /// the unique scan ID that was returned by + /// + public Task GetScanStatusAsync(string id) + { + if(string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException(nameof(id)); + } + var url = new Uri($"{BasePath}/shodan/scan/{id}"); + return MakeRequestAsync(url); + } + + /// + /// This method returns an object containing all the services that the Shodan crawlers look at. It can also be used as a quick and practical way to resolve a port number to the name of a service + /// + /// + public Task> GetServicesAsync() + { + var url = new Uri($"{BasePath}/shodan/services?key={this.apikey}"); + return MakeRequestAsync>(url); + } + + /// + /// Use this method to obtain a list of popular tags for the saved search queries in Shodan. + /// + /// The number of tags to return + /// + public Task GetTagsAsync(int size = 10) + { + var url = new UriBuilder($"{BasePath}/shodan/query/tags") + { + Query = $"key={apikey}&size={size}" + }; + return MakeRequestAsync(url.Uri); + } + + /// + /// Use this method to request Shodan to crawl the Internet for a specific port. + /// This method is restricted to security researchers and companies with a Shodan Data license. To apply for access to this method as a researcher, please email jmath@shodan.io with information about your project. Access is restricted to prevent abuse. + /// + /// The port that Shodan should crawl the Internet for. + /// The name of the protocol that should be used to interrogate the port. See for a list of supported protocols. + /// + public Task RequestInternetPortScanAsync(int port, string protocol) + { + var url = new Uri($"{BasePath}/shodan/scan/internet?key={this.apikey}"); + using(var data = new FormUrlEncodedContent(new List>() { + new KeyValuePair("port", port.ToString()), + new KeyValuePair("protocol", protocol) + })) + { + return MakeRequestAsync(url, data, RequestType.POST); + } + } + + /// + /// Use this method to request Shodan to crawl a network + /// Requirements: This method uses API scan credits: 1 IP consumes 1 scan credit. You must have a paid API plan (either one-time payment or subscription) in order to use this method + /// + /// + /// + public Task RequstScanAsync(string ips) + { + if(string.IsNullOrWhiteSpace(ips)) + { + throw new ArgumentNullException(nameof(ips)); + } + if(!ips.Split(',').Any()) + { + throw new ArgumentOutOfRangeException($"{ips} must have one valid record"); + } + var url = new Uri($"{BasePath}/shodan/scan?key={this.apikey}"); + using(var data = new FormUrlEncodedContent(new KeyValuePair[] { new KeyValuePair("ips", ips) })) + { + return MakeRequestAsync(url, data, RequestType.POST); + } + } + + /// + /// Look up the hostnames that have been defined for the given list of IP addresses + /// + /// Comma-separated list of IP addresses; example "74.125.227.230,204.79.197.200" + /// + public Task>> ReverseLookupAsync(string ips) + { + if(string.IsNullOrWhiteSpace(ips)) + { + throw new ArgumentNullException(ips); + } + var url = new Uri($"{BasePath}/dns/reverse?ips={ips}&key={this.apikey}"); + return MakeRequestAsync>>(url); + } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/ShodanException.cs b/src/Shodan.Net/ShodanException.cs new file mode 100644 index 0000000..d498acc --- /dev/null +++ b/src/Shodan.Net/ShodanException.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Shodan.Net +{ + public class ShodanException : Exception + { + public ShodanException() + { + } + + public ShodanException(string message) : base(message) + { + } + + public ShodanException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Shodan.Net/project.json b/src/Shodan.Net/project.json new file mode 100644 index 0000000..95d8e59 --- /dev/null +++ b/src/Shodan.Net/project.json @@ -0,0 +1,17 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "System.Runtime.Serialization.Primitives": "4.1.1-rc2-24027", + "System.Dynamic.Runtime": "4.0.11-rc2-24027", + "Newtonsoft.Json": "8.0.3", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24027" + }, + + "frameworks": { + "netstandard1.5": { + "imports": "dnxcore50" + } + } +} \ No newline at end of file