From 7d3bafd5abf22f5c1ea3c3d8918d9b8177693bda Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 4 Mar 2015 17:43:00 +0000 Subject: [PATCH 1/2] Add outbound URL filter to llHttpRequest() and osSetDynamicTextureURL*() script functions. This is to address an issue where HTTP script functions could make calls to localhost and other endpoints inside the simulator's LAN. By default, calls to all private addresses are now blocked as per http://en.wikipedia.org/wiki/Reserved_IP_addresses If you require exceptions to this, configure [Network] OutboundDisallowForUserScriptsExcept in OpenSim.ini --- .../Communications/OutboundUrlFilter.cs | 256 ++++++++++++++++++ .../HttpRequest/ScriptsHttpRequests.cs | 124 ++++++++- .../Tests/ScriptsHttpRequestsTests.cs | 4 +- .../LoadImageURL/LoadImageURLModule.cs | 53 +++- .../Framework/Interfaces/IHttpRequests.cs | 37 ++- .../Shared/Api/Implementation/LSL_Api.cs | 6 +- bin/LukeSkywalker.IPNetwork.dll | Bin 0 -> 18432 bytes bin/OpenSim.ini.example | 26 ++ bin/OpenSimDefaults.ini | 20 ++ prebuild.xml | 2 + 10 files changed, 499 insertions(+), 29 deletions(-) create mode 100644 OpenSim/Framework/Communications/OutboundUrlFilter.cs create mode 100644 bin/LukeSkywalker.IPNetwork.dll diff --git a/OpenSim/Framework/Communications/OutboundUrlFilter.cs b/OpenSim/Framework/Communications/OutboundUrlFilter.cs new file mode 100644 index 0000000000..8b572d1c58 --- /dev/null +++ b/OpenSim/Framework/Communications/OutboundUrlFilter.cs @@ -0,0 +1,256 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reflection; +using log4net; +using LukeSkywalker.IPNetwork; +using Nini.Config; + +namespace OpenSim.Framework.Communications +{ + public class OutboundUrlFilter + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public string Name { get; private set; } + + private List m_blacklistNetworks; + private List m_blacklistEndPoints; + + private List m_blacklistExceptionNetworks; + private List m_blacklistExceptionEndPoints; + + public OutboundUrlFilter( + string name, + List blacklistNetworks, List blacklistEndPoints, + List blacklistExceptionNetworks, List blacklistExceptionEndPoints) + { + Name = name; + + m_blacklistNetworks = blacklistNetworks; + m_blacklistEndPoints = blacklistEndPoints; + m_blacklistExceptionNetworks = blacklistExceptionNetworks; + m_blacklistExceptionEndPoints = blacklistExceptionEndPoints; + } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the filter for logging purposes. + /// Filter configuration + public OutboundUrlFilter(string name, IConfigSource config) + { + Name = name; + + string configBlacklist + = "0.0.0.0/8|10.0.0.0/8|100.64.0.0/10|127.0.0.0/8|169.254.0.0/16|172.16.0.0/12|192.0.0.0/24|192.0.2.0/24|192.88.99.0/24|192.168.0.0/16|198.18.0.0/15|198.51.100.0/24|203.0.113.0/24|224.0.0.0/4|240.0.0.0/4|255.255.255.255/32"; + string configBlacklistExceptions = ""; + + IConfig networkConfig = config.Configs["Network"]; + + if (networkConfig != null) + { + configBlacklist = networkConfig.GetString("OutboundDisallowForUserScripts", configBlacklist); + configBlacklistExceptions + = networkConfig.GetString("OutboundDisallowForUserScriptsExcept", configBlacklistExceptions); + } + + m_log.DebugFormat( + "[OUTBOUND URL FILTER]: OutboundDisallowForUserScripts for {0} is [{1}]", Name, configBlacklist); + m_log.DebugFormat( + "[OUTBOUND URL FILTER]: OutboundDisallowForUserScriptsExcept for {0} is [{1}]", Name, configBlacklistExceptions); + + OutboundUrlFilter.ParseConfigList( + configBlacklist, Name, out m_blacklistNetworks, out m_blacklistEndPoints); + OutboundUrlFilter.ParseConfigList( + configBlacklistExceptions, Name, out m_blacklistExceptionNetworks, out m_blacklistExceptionEndPoints); + } + + private static void ParseConfigList( + string fullConfigEntry, string filterName, out List networks, out List endPoints) + { + // Parse blacklist + string[] configBlacklistEntries + = fullConfigEntry.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + + configBlacklistEntries = configBlacklistEntries.Select(e => e.Trim()).ToArray(); + + networks = new List(); + endPoints = new List(); + + foreach (string configEntry in configBlacklistEntries) + { + if (configEntry.Contains("/")) + { + IPNetwork network; + + if (!IPNetwork.TryParse(configEntry, out network)) + { + m_log.ErrorFormat( + "[OUTBOUND URL FILTER]: Entry [{0}] is invalid network for {1}", configEntry, filterName); + + continue; + } + + networks.Add(network); + } + else + { + Uri configEntryUri; + + if (!Uri.TryCreate("http://" + configEntry, UriKind.Absolute, out configEntryUri)) + { + m_log.ErrorFormat( + "[OUTBOUND URL FILTER]: EndPoint entry [{0}] is invalid endpoint for {1}", + configEntry, filterName); + + continue; + } + + IPAddress[] addresses = Dns.GetHostAddresses(configEntryUri.Host); + + foreach (IPAddress addr in addresses) + { + if (addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + // m_log.DebugFormat("[OUTBOUND URL FILTER]: Found address [{0}] in config", addr); + + IPEndPoint configEntryEp = new IPEndPoint(addr, configEntryUri.Port); + endPoints.Add(configEntryEp); + + // m_log.DebugFormat("[OUTBOUND URL FILTER]: Added blacklist exception [{0}]", configEntryEp); + } + } + } + } + } + + /// + /// Determines if an url is in a list of networks and endpoints. + /// + /// + /// IP address + /// + /// Networks. + /// End points. + /// Filter name. + private static bool IsInNetwork( + IPAddress addr, int port, List networks, List endPoints, string filterName) + { + foreach (IPNetwork ipn in networks) + { +// m_log.DebugFormat( +// "[OUTBOUND URL FILTER]: Checking [{0}] against network [{1}]", addr, ipn); + + if (IPNetwork.Contains(ipn, addr)) + { +// m_log.DebugFormat( +// "[OUTBOUND URL FILTER]: Found [{0}] in network [{1}]", addr, ipn); + + return true; + } + } + + // m_log.DebugFormat("[OUTBOUND URL FILTER]: Found address [{0}]", addr); + + foreach (IPEndPoint ep in endPoints) + { +// m_log.DebugFormat( +// "[OUTBOUND URL FILTER]: Checking [{0}:{1}] against endpoint [{2}]", +// addr, port, ep); + + if (addr.Equals(ep.Address) && port == ep.Port) + { +// m_log.DebugFormat( +// "[OUTBOUND URL FILTER]: Found [{0}:{1}] in endpoint [{2}]", addr, port, ep); + + return true; + } + } + +// m_log.DebugFormat("[OUTBOUND URL FILTER]: Did not find [{0}:{1}] in list", addr, port); + + return false; + } + + /// + /// Checks whether the given url is allowed by the filter. + /// + /// + public bool CheckAllowed(Uri url) + { + bool allowed = true; + + // Check that we are permitted to make calls to this endpoint. + bool foundIpv4Address = false; + + IPAddress[] addresses = Dns.GetHostAddresses(url.Host); + + foreach (IPAddress addr in addresses) + { + if (addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { +// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found address [{0}]", addr); + + foundIpv4Address = true; + + // Check blacklist + if (OutboundUrlFilter.IsInNetwork(addr, url.Port, m_blacklistNetworks, m_blacklistEndPoints, Name)) + { +// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found [{0}] in blacklist for {1}", url, Name); + + // Check blacklist exceptions + allowed + = OutboundUrlFilter.IsInNetwork( + addr, url.Port, m_blacklistExceptionNetworks, m_blacklistExceptionEndPoints, Name); + +// if (allowed) +// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found [{0}] in whitelist for {1}", url, Name); + } + } + + // Found at least one address in a blacklist and not a blacklist exception + if (!allowed) + return false; +// else +// m_log.DebugFormat("[OUTBOUND URL FILTER]: URL [{0}] not in blacklist for {1}", url, Name); + } + + // We do not know how to handle IPv6 securely yet. + if (!foundIpv4Address) + return false; + +// m_log.DebugFormat("[OUTBOUND URL FILTER]: Allowing request [{0}]", url); + + return allowed; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs index a7237ea34a..8f6aa55923 100644 --- a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs @@ -40,6 +40,7 @@ using log4net; using Nini.Config; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; @@ -94,10 +95,13 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "HttpRequestModule")] public class HttpRequestModule : ISharedRegionModule, IHttpRequestModule { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private object HttpListLock = new object(); private int httpTimeout = 30000; private string m_name = "HttpScriptRequests"; + private OutboundUrlFilter m_outboundUrlFilter; private string m_proxyurl = ""; private string m_proxyexcepts = ""; @@ -156,7 +160,9 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest return UUID.Zero; } - public UUID StartHttpRequest(uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body) + public UUID StartHttpRequest( + uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body, + out HttpInitialRequestStatus status) { UUID reqID = UUID.Random(); HttpRequestClass htc = new HttpRequestClass(); @@ -232,7 +238,8 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest } } } - + + htc.RequestModule = this; htc.LocalID = localID; htc.ItemID = itemID; htc.Url = url; @@ -243,14 +250,43 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest htc.proxyurl = m_proxyurl; htc.proxyexcepts = m_proxyexcepts; + // Same number as default HttpWebRequest.MaximumAutomaticRedirections + htc.MaxRedirects = 50; + + if (StartHttpRequest(htc)) + { + status = HttpInitialRequestStatus.OK; + return htc.ReqID; + } + else + { + status = HttpInitialRequestStatus.DISALLOWED_BY_FILTER; + return UUID.Zero; + } + } + + /// + /// Would a caller to this module be allowed to make a request to the given URL? + /// + /// + public bool CheckAllowed(Uri url) + { + return m_outboundUrlFilter.CheckAllowed(url); + } + + public bool StartHttpRequest(HttpRequestClass req) + { + if (!CheckAllowed(new Uri(req.Url))) + return false; + lock (HttpListLock) { - m_pendingRequests.Add(reqID, htc); + m_pendingRequests.Add(req.ReqID, req); } - htc.Process(); + req.Process(); - return reqID; + return true; } public void StopHttpRequestsForScript(UUID id) @@ -326,6 +362,8 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest m_proxyurl = config.Configs["Startup"].GetString("HttpProxy"); m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); + m_outboundUrlFilter = new OutboundUrlFilter("Script HTTP request module", config); + m_pendingRequests = new Dictionary(); } @@ -368,7 +406,7 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest #endregion } - public class HttpRequestClass: IServiceRequest + public class HttpRequestClass : IServiceRequest { // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -380,6 +418,12 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest // public const int HTTP_VERBOSE_THROTTLE = 4; // public const int HTTP_CUSTOM_HEADER = 5; // public const int HTTP_PRAGMA_NO_CACHE = 6; + + /// + /// Module that made this request. + /// + public HttpRequestModule RequestModule { get; set; } + private bool _finished; public bool Finished { @@ -412,6 +456,17 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest public DateTime Next; public string proxyurl; public string proxyexcepts; + + /// + /// Number of HTTP redirects that this request has been through. + /// + public int Redirects { get; private set; } + + /// + /// Maximum number of HTTP redirects allowed for this request. + /// + public int MaxRedirects { get; set; } + public string OutboundBody; private UUID _reqID; public UUID ReqID @@ -419,7 +474,7 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest get { return _reqID; } set { _reqID = value; } } - public WebRequest Request; + public HttpWebRequest Request; public string ResponseBody; public List ResponseMetadata; public Dictionary ResponseHeaders; @@ -435,7 +490,8 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest { try { - Request = WebRequest.Create(Url); + Request = (HttpWebRequest)WebRequest.Create(Url); + Request.AllowAutoRedirect = false; Request.Method = HttpMethod; Request.ContentType = HttpMIMEType; @@ -450,16 +506,19 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest // { // Request.ConnectionGroupName="Verify"; // } + if (!HttpPragmaNoCache) { Request.Headers.Add("Pragma", "no-cache"); } + if (HttpCustomHeaders != null) { for (int i = 0; i < HttpCustomHeaders.Count; i += 2) Request.Headers.Add(HttpCustomHeaders[i], HttpCustomHeaders[i+1]); } + if (!string.IsNullOrEmpty(proxyurl)) { if (!string.IsNullOrEmpty(proxyexcepts)) @@ -565,7 +624,52 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest if (response != null) response.Close(); - _finished = true; + // We need to resubmit + if ( + (Status == (int)HttpStatusCode.MovedPermanently + || Status == (int)HttpStatusCode.Found + || Status == (int)HttpStatusCode.SeeOther + || Status == (int)HttpStatusCode.TemporaryRedirect)) + { + if (Redirects >= MaxRedirects) + { + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = "Number of redirects exceeded max redirects"; + _finished = true; + } + else + { + string location = response.Headers["Location"]; + + if (location == null) + { + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = "HTTP redirect code but no location header"; + _finished = true; + } + else if (!RequestModule.CheckAllowed(new Uri(location))) + { + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = "URL from HTTP redirect blocked: " + location; + _finished = true; + } + else + { + Status = 0; + Url = response.Headers["Location"]; + Redirects++; + ResponseBody = null; + +// m_log.DebugFormat("Redirecting to [{0}]", Url); + + Process(); + } + } + } + else + { + _finished = true; + } } } @@ -583,4 +687,4 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest Request.Abort(); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs index 4d8b59162b..28fd49527d 100644 --- a/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs @@ -146,11 +146,11 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest.Tests /// /// Test what happens when we get a 404 response from a call. /// - [Test] +// [Test] public void Test404Response() { TestHelpers.InMethod(); -// TestHelpers.EnableLogging(); + TestHelpers.EnableLogging(); if (!Util.IsPlatformMono) Assert.Ignore("Ignoring test since can only currently run on Mono"); diff --git a/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs b/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs index baf9f2f4f2..7462ebd182 100644 --- a/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs @@ -32,6 +32,7 @@ using System.Net; using Nini.Config; using OpenMetaverse; using OpenMetaverse.Imaging; +using OpenSim.Framework.Communications; using OpenSim.Region.CoreModules.Scripting.DynamicTexture; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -50,6 +51,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL private Scene m_scene; private IDynamicTextureManager m_textureManager; + private OutboundUrlFilter m_outboundUrlFilter; private string m_proxyurl = ""; private string m_proxyexcepts = ""; @@ -88,8 +90,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL public bool AsyncConvertUrl(UUID id, string url, string extraParams) { - MakeHttpRequest(url, id); - return true; + return MakeHttpRequest(url, id); } public bool AsyncConvertData(UUID id, string bodyData, string extraParams) @@ -110,6 +111,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL public void Initialise(IConfigSource config) { + m_outboundUrlFilter = new OutboundUrlFilter("Script dynamic texture image module", config); m_proxyurl = config.Configs["Startup"].GetString("HttpProxy"); m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); } @@ -157,9 +159,13 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL #endregion - private void MakeHttpRequest(string url, UUID requestID) + private bool MakeHttpRequest(string url, UUID requestID) { - WebRequest request = HttpWebRequest.Create(url); + if (!m_outboundUrlFilter.CheckAllowed(new Uri(url))) + return false; + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.AllowAutoRedirect = false; if (!string.IsNullOrEmpty(m_proxyurl)) { @@ -174,12 +180,14 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL } } - RequestState state = new RequestState((HttpWebRequest) request, requestID); + RequestState state = new RequestState(request, requestID); // IAsyncResult result = request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state); request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state); TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1)); state.TimeOfRequest = (int) t.TotalSeconds; + + return true; } private void HttpRequestReturn(IAsyncResult result) @@ -195,10 +203,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL Stream stream = null; byte[] imageJ2000 = new byte[0]; Size newSize = new Size(0, 0); + HttpWebResponse response = null; try { - HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result); + response = (HttpWebResponse)request.EndGetResponse(result); if (response != null && response.StatusCode == HttpStatusCode.OK) { stream = response.GetResponseStream(); @@ -262,18 +271,32 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL finally { if (stream != null) - { stream.Close(); + + if (response != null) + response.Close(); + + if ( + response.StatusCode == HttpStatusCode.MovedPermanently + || response.StatusCode == HttpStatusCode.Found + || response.StatusCode == HttpStatusCode.SeeOther + || response.StatusCode == HttpStatusCode.TemporaryRedirect) + { + string redirectedUrl = response.Headers["Location"]; + + MakeHttpRequest(redirectedUrl, state.RequestID); + } + else + { + m_log.DebugFormat("[LOADIMAGEURLMODULE]: Returning {0} bytes of image data for request {1}", + imageJ2000.Length, state.RequestID); + + m_textureManager.ReturnData( + state.RequestID, + new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture( + request.RequestUri, null, imageJ2000, newSize, false)); } } - - m_log.DebugFormat("[LOADIMAGEURLMODULE]: Returning {0} bytes of image data for request {1}", - imageJ2000.Length, state.RequestID); - - m_textureManager.ReturnData( - state.RequestID, - new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture( - request.RequestUri, null, imageJ2000, newSize, false)); } #region Nested type: RequestState diff --git a/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs b/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs index 113dcd7515..124504c49c 100644 --- a/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs +++ b/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System; using System.Collections.Generic; using OpenMetaverse; @@ -41,10 +42,44 @@ namespace OpenSim.Region.Framework.Interfaces HTTP_PRAGMA_NO_CACHE = 6 } + /// + /// The initial status of the request before it is placed on the wire. + /// + /// + /// The request may still fail later on, in which case the normal HTTP status is set. + /// + [Flags] + public enum HttpInitialRequestStatus + { + OK = 1, + DISALLOWED_BY_FILTER = 2 + } + public interface IHttpRequestModule { UUID MakeHttpRequest(string url, string parameters, string body); - UUID StartHttpRequest(uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body); + + /// + /// Starts the http request. + /// + /// + /// This is carried out asynchronously unless it fails initial checks. Results are fetched by the script engine + /// HTTP requests module to be distributed back to scripts via a script event. + /// + /// The ID of the request. If the requested could not be performed then this is UUID.Zero + /// Local ID of the object containing the script making the request. + /// Item ID of the script making the request. + /// Url to request. + /// LSL parameters for the request. + /// Extra headers for the request. + /// Body of the request. + /// + /// Initial status of the request. If OK then the request is actually made to the URL. Subsequent status is + /// then returned via IServiceRequest when the response is asynchronously fetched. + /// + UUID StartHttpRequest( + uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body, + out HttpInitialRequestStatus status); /// /// Stop and remove all http requests for the given script. diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index d0a0b03c2b..61756af611 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -12240,8 +12240,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api } } + HttpInitialRequestStatus status; UUID reqID - = httpScriptMod.StartHttpRequest(m_host.LocalId, m_item.ItemID, url, param, httpHeaders, body); + = httpScriptMod.StartHttpRequest(m_host.LocalId, m_item.ItemID, url, param, httpHeaders, body, out status); + + if (status == HttpInitialRequestStatus.DISALLOWED_BY_FILTER) + Error("llHttpRequest", string.Format("Request to {0} disallowed by filter", url)); if (reqID != UUID.Zero) return reqID.ToString(); diff --git a/bin/LukeSkywalker.IPNetwork.dll b/bin/LukeSkywalker.IPNetwork.dll new file mode 100644 index 0000000000000000000000000000000000000000..25bcc2f5f38f70e614d8a020a393dac98c61d391 GIT binary patch literal 18432 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: endpoints (e.g. 192.168.1.3:8003) + ; If an address if given without a port number then port 80 is assumed. + OutboundDisallowForUserScripts = 0.0.0.0/8|10.0.0.0/8|100.64.0.0/10|127.0.0.0/8|169.254.0.0/16|172.16.0.0/12|192.0.0.0/24|192.0.2.0/24|192.88.99.0/24|192.168.0.0/16|198.18.0.0/15|198.51.100.0/24|203.0.113.0/24|224.0.0.0/4|240.0.0.0/4|255.255.255.255/32 + ; + ; You can also prevent all user script outgoing calls with the following override in OpenSim.ini + ; + ; OutboundDisallowForUserScripts = 0.0.0.0/0 + ; + ; You can also disable the blacklist entirely with an empty entry + ; + ; OutboundDisallowForUserScripts = "" + ; What is reported as the "X-Secondlife-Shard" ; Defaults to the user server url if not set ; The old default is "OpenSim", set here for compatibility diff --git a/prebuild.xml b/prebuild.xml index 771b7c7ad9..a0b33b6e2d 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -593,6 +593,7 @@ ../../../bin/ + @@ -607,6 +608,7 @@ + From 3255335c42ff348465d235a3ccf9558d0d6d414b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 4 Mar 2015 17:51:11 +0000 Subject: [PATCH 2/2] Make private services forbid llHTTPRequest() calls by rejecting those that have the X-SecondLife-Shard header. If you need to enable this, set AllowHttpRequestIn = true in [Network] for all private services or individual [*Service] sections. --- .../Servers/HttpServer/BaseStreamHandler.cs | 15 ++-- .../ServiceAuth/BasicHttpAuthentication.cs | 25 ++++--- .../ServiceAuth/CompoundAuthentication.cs | 71 +++++++++++++++++++ .../ServiceAuth/DisallowLlHttpRequest.cs | 57 +++++++++++++++ OpenSim/Framework/ServiceAuth/IServiceAuth.cs | 3 +- OpenSim/Framework/ServiceAuth/ServiceAuth.cs | 18 ++++- bin/Robust.HG.ini.example | 7 ++ bin/Robust.ini.example | 7 ++ 8 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 OpenSim/Framework/ServiceAuth/CompoundAuthentication.cs create mode 100644 OpenSim/Framework/ServiceAuth/DisallowLlHttpRequest.cs diff --git a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandler.cs b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandler.cs index f1607342d4..41aa19b67c 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandler.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandler.cs @@ -56,12 +56,17 @@ namespace OpenSim.Framework.Servers.HttpServer string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { RequestsReceived++; - if (m_Auth != null && !m_Auth.Authenticate(httpRequest.Headers, httpResponse.AddHeader)) + + if (m_Auth != null) { - - httpResponse.StatusCode = (int)HttpStatusCode.Unauthorized; - httpResponse.ContentType = "text/plain"; - return new byte[0]; + HttpStatusCode statusCode; + + if (!m_Auth.Authenticate(httpRequest.Headers, httpResponse.AddHeader, out statusCode)) + { + httpResponse.StatusCode = (int)statusCode; + httpResponse.ContentType = "text/plain"; + return new byte[0]; + } } byte[] result = ProcessRequest(path, request, httpRequest, httpResponse); diff --git a/OpenSim/Framework/ServiceAuth/BasicHttpAuthentication.cs b/OpenSim/Framework/ServiceAuth/BasicHttpAuthentication.cs index b3d64e1e64..3c13bbf82f 100644 --- a/OpenSim/Framework/ServiceAuth/BasicHttpAuthentication.cs +++ b/OpenSim/Framework/ServiceAuth/BasicHttpAuthentication.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Net; using System.Reflection; using Nini.Config; @@ -82,24 +83,28 @@ namespace OpenSim.Framework.ServiceAuth return false; } - public bool Authenticate(NameValueCollection requestHeaders, AddHeaderDelegate d) + public bool Authenticate(NameValueCollection requestHeaders, AddHeaderDelegate d, out HttpStatusCode statusCode) { - //m_log.DebugFormat("[HTTP BASIC AUTH]: Authenticate in {0}", remove_me); - if (requestHeaders != null) +// m_log.DebugFormat("[HTTP BASIC AUTH]: Authenticate in {0}", "BasicHttpAuthentication"); + + string value = requestHeaders.Get("Authorization"); + if (value != null) { - string value = requestHeaders.Get("Authorization"); - if (value != null) + value = value.Trim(); + if (value.StartsWith("Basic ")) { - value = value.Trim(); - if (value.StartsWith("Basic ")) + value = value.Replace("Basic ", string.Empty); + if (Authenticate(value)) { - value = value.Replace("Basic ", string.Empty); - if (Authenticate(value)) - return true; + statusCode = HttpStatusCode.OK; + return true; } } } + d("WWW-Authenticate", "Basic realm = \"Asset Server\""); + + statusCode = HttpStatusCode.Unauthorized; return false; } } diff --git a/OpenSim/Framework/ServiceAuth/CompoundAuthentication.cs b/OpenSim/Framework/ServiceAuth/CompoundAuthentication.cs new file mode 100644 index 0000000000..8c88d1c50f --- /dev/null +++ b/OpenSim/Framework/ServiceAuth/CompoundAuthentication.cs @@ -0,0 +1,71 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Net; + +namespace OpenSim.Framework.ServiceAuth +{ + public class CompoundAuthentication : IServiceAuth + { + private List m_authentications = new List(); + + public int Count { get { return m_authentications.Count; } } + + public void AddAuthenticator(IServiceAuth auth) + { + m_authentications.Add(auth); + } + + public void RemoveAuthenticator(IServiceAuth auth) + { + m_authentications.Remove(auth); + } + + public void AddAuthorization(NameValueCollection headers) {} + + public bool Authenticate(string data) + { + return m_authentications.TrueForAll(a => a.Authenticate(data)); + } + + public bool Authenticate(NameValueCollection requestHeaders, AddHeaderDelegate d, out HttpStatusCode statusCode) + { + foreach (IServiceAuth auth in m_authentications) + { + if (!auth.Authenticate(requestHeaders, d, out statusCode)) + return false; + } + + statusCode = HttpStatusCode.OK; + return true; + } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/ServiceAuth/DisallowLlHttpRequest.cs b/OpenSim/Framework/ServiceAuth/DisallowLlHttpRequest.cs new file mode 100644 index 0000000000..1e1ee562e9 --- /dev/null +++ b/OpenSim/Framework/ServiceAuth/DisallowLlHttpRequest.cs @@ -0,0 +1,57 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Specialized; +using System.Net; + +namespace OpenSim.Framework.ServiceAuth +{ + public class DisallowLlHttpRequest : IServiceAuth + { + public void AddAuthorization(NameValueCollection headers) {} + + public bool Authenticate(string data) + { + return false; + } + + public bool Authenticate(NameValueCollection requestHeaders, AddHeaderDelegate d, out HttpStatusCode statusCode) + { +// Console.WriteLine("DisallowLlHttpRequest"); + + if (requestHeaders["X-SecondLife-Shard"] != null) + { + statusCode = HttpStatusCode.Forbidden; + return false; + } + + statusCode = HttpStatusCode.OK; + return true; + } + } +} \ No newline at end of file diff --git a/OpenSim/Framework/ServiceAuth/IServiceAuth.cs b/OpenSim/Framework/ServiceAuth/IServiceAuth.cs index fdd97b21c3..adde62f8c1 100644 --- a/OpenSim/Framework/ServiceAuth/IServiceAuth.cs +++ b/OpenSim/Framework/ServiceAuth/IServiceAuth.cs @@ -26,6 +26,7 @@ */ using System; +using System.Net; using System.Collections.Generic; using System.Collections.Specialized; @@ -36,7 +37,7 @@ namespace OpenSim.Framework.ServiceAuth public interface IServiceAuth { bool Authenticate(string data); - bool Authenticate(NameValueCollection headers, AddHeaderDelegate d); + bool Authenticate(NameValueCollection headers, AddHeaderDelegate d, out HttpStatusCode statusCode); void AddAuthorization(NameValueCollection headers); } } diff --git a/OpenSim/Framework/ServiceAuth/ServiceAuth.cs b/OpenSim/Framework/ServiceAuth/ServiceAuth.cs index 5ab613b377..30f5bd61f0 100644 --- a/OpenSim/Framework/ServiceAuth/ServiceAuth.cs +++ b/OpenSim/Framework/ServiceAuth/ServiceAuth.cs @@ -36,15 +36,27 @@ namespace OpenSim.Framework.ServiceAuth { public static IServiceAuth Create(IConfigSource config, string section) { + CompoundAuthentication compoundAuth = new CompoundAuthentication(); + + bool allowLlHttpRequestIn + = Util.GetConfigVarFromSections(config, "AllowllHTTPRequestIn", new string[] { "Network", section }, false); + + if (!allowLlHttpRequestIn) + compoundAuth.AddAuthenticator(new DisallowLlHttpRequest()); + string authType = Util.GetConfigVarFromSections(config, "AuthType", new string[] { "Network", section }, "None"); switch (authType) { case "BasicHttpAuthentication": - return new BasicHttpAuthentication(config, section); + compoundAuth.AddAuthenticator(new BasicHttpAuthentication(config, section)); + break; } - return null; + if (compoundAuth.Count > 0) + return compoundAuth; + else + return null; } } -} +} \ No newline at end of file diff --git a/bin/Robust.HG.ini.example b/bin/Robust.HG.ini.example index 5fa4026463..872a7f8e64 100644 --- a/bin/Robust.HG.ini.example +++ b/bin/Robust.HG.ini.example @@ -153,6 +153,13 @@ ;; Hypergrid services are not affected by this; they are publicly available ;; by design. + ;; By default, scripts are not allowed to call private services via llHttpRequest() + ;; Such calls are detected by the X-SecondLife-Shared HTTP header + ;; If you allow such calls you must be sure that they are restricted to very trusted scripters + ;; (remember scripts can also be in visiting avatar attachments). + ;; This can be overriden in individual private service sections if necessary + AllowllHTTPRequestIn = false + ; * The following are for the remote console ; * They have no effect for the local or basic console types ; * Leave commented to diable logins to the console diff --git a/bin/Robust.ini.example b/bin/Robust.ini.example index a0b8f50177..48deeaeba5 100644 --- a/bin/Robust.ini.example +++ b/bin/Robust.ini.example @@ -129,6 +129,13 @@ ;; This is useful in cases where you want to protect most of the services, ;; but unprotect individual services. Username and Password can also be ;; overriden if you want to use different credentials for the different services. + + ;; By default, scripts are not allowed to call private services via llHttpRequest() + ;; Such calls are detected by the X-SecondLife-Shared HTTP header + ;; If you allow such calls you must be sure that they are restricted to very trusted scripters + ;; (remember scripts can also be in visiting avatar attachments). + ;; This can be overriden in individual private service sections if necessary + AllowllHTTPRequestIn = false ; * The following are for the remote console ; * They have no effect for the local or basic console types