Skip to content

Commit 52f668d

Browse files
committed
restrict fetching
1 parent 5fc65c1 commit 52f668d

4 files changed

Lines changed: 70 additions & 12 deletions

File tree

cardano-db-sync/app/http-get-json-metadata.hs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import Data.ByteString.Lazy (fromStrict)
1515
import qualified Data.List as List
1616
import qualified Data.Text as Text
1717
import qualified Data.Text.IO as Text
18-
import qualified Network.HTTP.Client as Http
19-
import Network.HTTP.Client.TLS (tlsManagerSettings)
2018
import System.Console.ANSI (setSGRCode)
2119
import System.Console.ANSI.Types (
2220
Color (..),
@@ -108,7 +106,7 @@ runHttpGetPool poolUrl mHash =
108106
httpGet :: ExceptT OffChainFetchError IO SimplifiedOffChainPoolData
109107
httpGet = do
110108
request <- parseOffChainUrl $ OffChainPoolUrl poolUrl
111-
manager <- liftIO $ Http.newManager tlsManagerSettings
109+
manager <- liftIO newRestrictedManager
112110
httpGetOffChainPoolData manager request poolUrl mHash
113111

114112
reportSuccess :: SimplifiedOffChainPoolData -> IO ()

cardano-db-sync/cardano-db-sync.cabal

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,10 @@ library
193193
, groups
194194
, hasql
195195
, http-client
196-
, http-client-tls
196+
, http-client-restricted
197197
, http-types
198198
, iohk-monitoring
199+
, network
199200
, lifted-base
200201
, memory
201202
, microlens
@@ -296,8 +297,6 @@ executable http-get-json-metadata
296297
, bytestring
297298
, cardano-db
298299
, cardano-db-sync
299-
, http-client
300-
, http-client-tls
301300
, text
302301
, transformers
303302

cardano-db-sync/src/Cardano/DbSync/OffChain.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import qualified Hasql.Connection as HsqlC
4545
import qualified Hasql.Pipeline as HsqlP
4646
import qualified Hasql.Session as HsqlSes
4747
import qualified Network.HTTP.Client as Http
48-
import Network.HTTP.Client.TLS (tlsManagerSettings)
4948

5049
---------------------------------------------------------------------------------------------------------------------------------
5150
-- Load OffChain Work Queue
@@ -254,7 +253,7 @@ runFetchOffChainPoolThread syncEnv = do
254253
DB.runDbTransLogged trce dbEnv mIsolationLevel $
255254
loadOffChainPoolWorkQueue trce (envOffChainPoolWorkQueue threadSyncEnv)
256255
poolq <- atomically $ flushTBQueue (envOffChainPoolWorkQueue threadSyncEnv)
257-
manager <- Http.newManager tlsManagerSettings
256+
manager <- newRestrictedManager
258257
now <- liftIO Time.getPOSIXTime
259258
mapM_ (queuePoolInsert <=< fetchOffChainPoolData trce manager now) poolq
260259
)

cardano-db-sync/src/Cardano/DbSync/OffChain/Http.hs

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Cardano.DbSync.OffChain.Http (
77
httpGetOffChainVoteData,
88
parseAndValidateVoteData,
99
parseOffChainUrl,
10+
newRestrictedManager,
1011
) where
1112

1213
import qualified Cardano.Crypto.Hash.Blake2b as Crypto
@@ -38,8 +39,9 @@ import qualified Data.Text.Encoding as Text
3839
import GHC.Show (show)
3940
import Network.HTTP.Client (HttpException (..))
4041
import qualified Network.HTTP.Client as Http
41-
import Network.HTTP.Client.TLS (tlsManagerSettings)
42+
import Network.HTTP.Client.Restricted (Restriction, addressRestriction, connectionRestricted, mkRestrictedManagerSettings)
4243
import qualified Network.HTTP.Types as Http
44+
import qualified Network.Socket as Socket
4345

4446
-------------------------------------------------------------------------------------
4547
-- Get OffChain data
@@ -103,7 +105,7 @@ httpGetOffChainVoteDataSingle ::
103105
DB.AnchorType ->
104106
ExceptT OffChainFetchError IO SimplifiedOffChainVoteData
105107
httpGetOffChainVoteDataSingle vurl metaHash anchorType = do
106-
manager <- liftIO $ Http.newManager tlsManagerSettings
108+
manager <- liftIO newRestrictedManager
107109
request <- parseOffChainUrl url
108110
let req = httpGetBytes manager request 3000000 3000000 url
109111
httpRes <- handleExceptT (convertHttpException url) req
@@ -216,8 +218,20 @@ isPossiblyJsonObject bs =
216218
-- Url
217219
-------------------------------------------------------------------------------------
218220
parseOffChainUrl :: OffChainUrlType -> ExceptT OffChainFetchError IO Http.Request
219-
parseOffChainUrl url =
220-
handleExceptT wrapHttpException $ applyContentType <$> Http.parseRequest (showUrl url)
221+
parseOffChainUrl url = do
222+
let urlText = Text.pack $ showUrl url
223+
unless (Text.isPrefixOf "https://" urlText || Text.isPrefixOf "http://" urlText) $
224+
left $
225+
OCFErrUrlParseFail url "Only HTTP/HTTPS URLs are allowed"
226+
request <- handleExceptT wrapHttpException $ Http.parseRequest (showUrl url)
227+
unless (Http.method request == "GET") $
228+
left $
229+
OCFErrUrlParseFail url "Only GET requests are allowed"
230+
let hostBS = Http.host request
231+
when (isLocalhostHost hostBS) $
232+
left $
233+
OCFErrUrlParseFail url "Access to localhost is not allowed"
234+
pure $ applyContentType request
221235
where
222236
wrapHttpException :: HttpException -> OffChainFetchError
223237
wrapHttpException err = OCFErrHttpException url (textShow err)
@@ -250,8 +264,56 @@ convertHttpException url he =
250264
OffChainPoolUrl _ -> OCFErrUrlParseFail (OffChainPoolUrl $ PoolUrl $ Text.pack urlx) (Text.pack err)
251265
OffChainVoteUrl _ -> OCFErrUrlParseFail (OffChainVoteUrl $ VoteUrl $ Text.pack urlx) (Text.pack err)
252266

267+
isLocalhostHost :: ByteString -> Bool
268+
isLocalhostHost host =
269+
host == "localhost"
270+
|| host == "127.0.0.1"
271+
|| host == "::1"
272+
|| host == "[::1]"
273+
|| BS.isPrefixOf "10." host
274+
|| BS.isPrefixOf "192.168." host
275+
253276
useIpfsGatewayMaybe :: VoteUrl -> [Text] -> Maybe [VoteUrl]
254277
useIpfsGatewayMaybe vu gateways =
255278
case Text.stripPrefix "ipfs://" (unVoteUrl vu) of
256279
Just sf -> Just $ VoteUrl . (<> sf) <$> gateways
257280
Nothing -> Nothing
281+
282+
-------------------------------------------------------------------------------------
283+
-- Restricted Manager
284+
-------------------------------------------------------------------------------------
285+
286+
-- | Create a restricted 'Http.ManagerSettings' that blocks connections to
287+
-- private, loopback, and link-local IP addresses. The restriction is
288+
-- checked at connect time on the resolved IP, so it applies to redirects
289+
-- and prevents DNS rebinding attacks.
290+
newRestrictedManager :: IO Http.Manager
291+
newRestrictedManager = do
292+
(settings, _mProxyRestricted) <- mkRestrictedManagerSettings offchainRestriction Nothing Nothing
293+
Http.newManager settings
294+
295+
offchainRestriction :: Restriction
296+
offchainRestriction = addressRestriction $ \addr ->
297+
if isPrivateAddr (Socket.addrAddress addr)
298+
then Just $ connectionRestricted ("Access to private, loopback, or link-local IP address is not allowed: " ++) addr
299+
else Nothing
300+
301+
isPrivateAddr :: Socket.SockAddr -> Bool
302+
isPrivateAddr (Socket.SockAddrInet _ hostAddr) =
303+
let (a, b, _, _) = Socket.hostAddressToTuple hostAddr
304+
in a == 0 -- 0.0.0.0/8 (current network)
305+
|| a == 10 -- 10.0.0.0/8 (private)
306+
|| (a == 100 && b >= 64 && b <= 127) -- 100.64.0.0/10 (CGNAT)
307+
|| a == 127 -- 127.0.0.0/8 (loopback)
308+
|| (a == 169 && b == 254) -- 169.254.0.0/16 (link-local)
309+
|| (a == 172 && b >= 16 && b <= 31) -- 172.16.0.0/12 (private)
310+
|| (a == 192 && b == 168) -- 192.168.0.0/16 (private)
311+
|| (a == 198 && b >= 18 && b <= 19) -- 198.18.0.0/15 (benchmarking)
312+
|| a >= 224 -- 224.0.0.0+ (multicast + reserved + broadcast)
313+
isPrivateAddr (Socket.SockAddrInet6 _ _ hostAddr6 _) =
314+
let addr@(w1, _, _, _, _, _, _, _) = Socket.hostAddress6ToTuple hostAddr6
315+
in addr == (0, 0, 0, 0, 0, 0, 0, 0) -- ::
316+
|| addr == (0, 0, 0, 0, 0, 0, 0, 1) -- ::1
317+
|| (w1 .&. 0xFE00) == 0xFC00 -- fc00::/7 (ULA)
318+
|| (w1 .&. 0xFFC0) == 0xFE80 -- fe80::/10 (link-local)
319+
isPrivateAddr _ = False

0 commit comments

Comments
 (0)