vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.71k stars 26.62k forks source link

Docs: Misleading and potentially vulnerable example code of getting client's IP address #66305

Open Yihao-G opened 3 months ago

Yihao-G commented 3 months ago

What is the update you wish to see?

https://github.com/vercel/next.js/blob/4c48f3b580e6a65c9b285cf73d6a6f80e2f4dcbb/docs/02-app/02-api-reference/04-functions/headers.mdx?plain=1#L92-L114

The documentation above shows we can get the client's IP address by reading x-forwarded-for's leftmost segment. However, the leftmost value could be spoofed by the requester.

Is there any context that might help us understand?

See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#security_and_privacy_concerns and https://github.com/rack/rack-attack/issues/145#issuecomment-443743558

Does the docs page already exist? Please link to it.

https://nextjs.org/docs/app/api-reference/functions/headers#ip-address

masterbater commented 3 months ago

Why open an issue with this?

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

Yihao-G commented 3 months ago

Why open an issue with this?

@masterbater What did you mean by that?

If you are asking why this issue is necessary - I believe the code example in the documentation is vulnerable, and many people would just copy the example code into their codebase, so I opened this issue to draw attention and see if any Next.js maintainer could improve the code. I'm happy to raise a PR too, but I'm just not sure how to improve it (should I just delete this section, fix the example code, or put a banner about the risk of using this code snippet?)

If you're asking why I included those texts in the issue body - I opened this issue following the issue template provided here so that's why the issue includes those details. The existence of the documentation issue template also indicates it's legitimate to create an issue to ask for improving the documentation.

If you are asking why I didn't report this to them privately as a vulnerability - I don't think it's a security vulnerability directly relating to Next.js. The issue is about the example code in the documentation is potentially dangerous if used in a production environment. The code itself isn't vulnerable because it's not executable. But if the Next.js maintainers think this issue should be pulled down, I am also ok with that.

masterbater commented 3 months ago

I see what you meant, the example grabs the first index. I think most load balancer(nginx,alb,what vercel use) will put the client ip in left most too.

The X-Forwarded-For header is untrustworthy when no trusted reverse proxy (e.g., a load balancer) is between the client and server

Yihao-G commented 3 months ago

It becomes an issue if the requester deliberately adds an XFF header, and load balancers append the client IP to the XFF header (e.g. AWS ALB does this by default).

The XFF header isn't trustworthy unless you know how many levels of proxies (ALB, CDN, etc.) are between the server and the public network.

ms4ever7 commented 2 months ago

Hi guys, I am using next 14.2.3. Sorry, maybe thats not about this exect topic, but we currently are having this issue form the PSI report - Internal IP disclosed in response or document. And on one page on our website it returns data like this in NextScript tag in body:

 <body>
        <div id="__next"></div>
        <script id="__NEXT_DATA__" type="application/json">
            {
                "props": {
                    "appConfig": {
                        "shopId": 8,
                        "shopName": "Zoom",
                        "version": "20240618115611",
                        "enabledFeatureFlags": [
                            "DETAILED_ERROR_LOGGING_ENABLED",
                            "PROMOTION_PAGE_ENABLED",
                            "PICKING_SITE_CONTENT",
                            "NEW_POSTCODE_CHECKER",
                            "ONE_TIME_PAYMENTS_ENABLED",
                            "DYNAMIC_YIELD_ENABLED",
                            "LMS_VIEW_SERVICE",
                            "CITRUS_AD_ENABLED",
                            "PICKING_SITE_ID_WITH_AVAILABILITY_ENABLED",
                            "UNATTENDED_DELIVERY_ENABLED",
                            "SSO_ENABLED",
                            "PICKING_SITE_ID_FOR_CATALOGUE_ENABLED",
                            "ONE_TRUST_ENABLED",
                            "LEGITIMATE_INTEREST_ENABLED",
                            "REDIRECT_TO_ADDRESS_PAGE_IF_LEEDS_ENABLED",
                            "PICKING_SITE_ID_ENABLED",
                            "NEW_STUART_SLOT_SELECTOR",
                            "RATE_LIMITING_ENABLED",
                            "STUART_SLOT_PRESELECTED",
                            "NEW_SEARCH_SORTING_ALGORITHM",
                            "WEB_CLIENT",
                            "ADVISE_V3_ENABLED"
                        ],
                        "enabledPlatformFeatures": {
                            "vanDeliveryEnabled": false,
                            "courierDeliveryEnabled": false,
                            "stuartDeliveryEnabled": true,
                            "deliveryInstructionsEnabled": true,
                            "marketingSelectedPartnersEnabled": true,
                            "showPreAuthorisationChargeInfo": false,
                            "orderLeaveFeedbackEnabled": true,
                            "fopUnitPriceEnabled": true,
                            "consentsVoucherEnabled": false,
                            "checkoutShowProducts": false,
                            "basketShowDeliveryInfo": false,
                            "basketShowDeliveryPrice": false,
                            "basketShowDetailsInHeader": false,
                            "badgesPriceMatch": true,
                            "badgesNewProduct": true,
                            "mentionMeEnabled": false,
                            "paypalPaymentEnabled": true,
                            "facebookLikeEnabled": false,
                            "showBopBrandEnabled": false,
                            "showBopPricePerEnabled": true,
                            "showBopSkuEnabled": false,
                            "fulfilmentAvailabilityMessageEnabled": true
                        },
                        "openingTime": {
                            "nextOpeningTime": 1718838000,
                            "nextClosingTime": 1718838000,
                            "currentTime": 1718817350,
                            "openingWeekDayFrom": 1,
                            "openingWeekDayTo": 7,
                            "openingHourFrom": 0,
                            "openingMinuteFrom": 0,
                            "openingHourTo": 0,
                            "openingMinuteTo": 0,
                            "showShopClosedPage": false,
                            "timeZone": "Europe/London",
                            "closeSoon": {
                                "title": "Check out now to get your Zoom",
                                "richText": "We're closing 12:00 AM.\u003cbr/\u003eCheck out now for delivery today.",
                                "plainText": "We're closing 12:00 AM. Check out now for delivery today."
                            },
                            "closeNow": {
                                "title": "We're open from 12:00 AM to 12:00 AM.",
                                "richText": "You can browse our range, but you won't be able to place an order. \u003cbr/\u003e\u003cbr/\u003e\u003cstrong\u003eOpening Hours: 12:00 AM to 12:00 AM\u003c/strong\u003e",
                                "plainText": "You can browse our range, but you won't be able to place an order. Opening Hours: 12:00 AM to 12:00 AM"
                            },
                            "shopOpen": true
                        },
                        "googleMapsApiKey": "AIzaSyAXRx1rCoNYf2IU78kIv4BScm-lr0doeZ4",
                        "addressCountry": "UK",
                        "defaultCurrency": "GBP",
                        "fulfilmentAvailabilityPollingIntervalSeconds": "90",
                        "mentionMeUrlReferrer": "https://tag.mention-me.com/api/v2/referreroffer/",
                        "mentionMeUrlReferee": "https://tag.mention-me.com/api/v2/refereefind/",
                        "mentionMePartnerCode": "mm4c4f97d8",
                        "mentionMeReferrerPopupDelaySeconds": "2",
                        "appStoreUrl": "https://itunes.apple.com/us/app/ocado-zoom-60-minute-groceries/id1447126432",
                        "playStoreUrl": "https://play.google.com/store/apps/details?id=com.ocado.gm.mobile.zoom",
                        "appStoreAppId": "1447126432",
                        "footerEmail": "zoom@ocado.com",
                        "footerPhoneNumber": "0345 600 2000",
                        "facebookAppId": "0",
                        "trustpilotUrl": "",
                        "socialMediaLinks": {
                            "facebook": "https://www.facebook.com/ZoombyOcado/",
                            "instagram": "https://www.instagram.com/ocadozoom/",
                            "twitter": "https://twitter.com/zoombyocado"
                        },
                        "userAgentIphoneSafari": false,
                        "loggedIn": false,
                        "userAgentIphoneNonSafari": false
                    },
                    "loggedIn": false,
                    "container": {
                    },
                    "logger": {
                    },
                    "pageProps": {
                        "loadingError": {
                            "isEdgeError": true,
                            "statusCode": 403,
                            "delegatedError": {
                                "message": "Request failed with status code 403",
                                "name": "AxiosError",
                                "stack": "AxiosError: Request failed with status code 403\n    at settle (/home/nobody/node_modules/axios/lib/core/settle.js:17:12)\n    at IncomingMessage.handleStreamEnd (/home/nobody/node_modules/axios/lib/adapters/http.js:382:11)\n    at IncomingMessage.emit (node:events:531:35)\n    at IncomingMessage.emit (node:domain:488:12)\n    at LegacyContextManager.runInContext (/home/nobody/node_modules/newrelic/lib/context-manager/legacy-context-manager.js:59:23)\n    at IncomingMessage.wrapped (/home/nobody/node_modules/newrelic/lib/transaction/tracer/index.js:203:37)\n    at IncomingMessage.wrappedResponseEmit (/home/nobody/node_modules/newrelic/lib/instrumentation/core/http-outbound.js:313:24)\n    at endReadableNT (node:internal/streams/readable:1696:12)\n    at runInContextCb (/home/nobody/node_modules/newrelic/lib/shim/shim.js:1315:22)\n    at LegacyContextManager.runInContext (/home/nobody/node_modules/newrelic/lib/context-manager/legacy-context-manager.js:59:23)",
                                "config": {
                                    "transitional": {
                                        "silentJSONParsing": true,
                                        "forcedJSONParsing": true,
                                        "clarifyTimeoutError": false
                                    },
                                    "transformRequest": [
                                        null
                                    ],
                                    "transformResponse": [
                                        null
                                    ],
                                    "timeout": 60000,
                                    "xsrfCookieName": "XSRF-TOKEN",
                                    "xsrfHeaderName": "X-XSRF-TOKEN",
                                    "maxContentLength": -1,
                                    "maxBodyLength": -1,
                                    "env": {
                                    },
                                    "headers": {
                                        "Accept": "application/json, text/plain, */*",
                                        "x-forwarded-for": "213.134.162.194, 15.158.16.106, 10.195.255.53",
                                        "x-forwarded-proto": "https",
                                        "x-forwarded-port": "443",
                                        "host": "zoom.ocado.com",
                                        "x-amzn-trace-id": "Self=1-66731246-015a3d1675c40f206cc5095d;Root=1-66731246-4519b7be237c847e7b5fa6fb",
                                        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
                                        "x-amz-cf-id": "p8yKpfEivvX1WnIRJL9_yMxmqU9KyLQWk03k79kxGD4qVqbqkw466Q==",
                                        "via": "2.0 de7bb21ade87bd155cf024cdea5bf62e.cloudfront.net (CloudFront)",
                                        "cookie": "AWSALB=yXVxK8aIDvAPH8rMUEyagDt3UJZm9Km82gODLKX29/Mwvh83SZ4mG3gtEbiNNM6koQvKRfXR710MVn+P1DULBLL0CMjR4K/gRl6QM0Tbhx0uH34aAL76ojmP5tL5; AWSALBCORS=yXVxK8aIDvAPH8rMUEyagDt3UJZm9Km82gODLKX29/Mwvh83SZ4mG3gtEbiNNM6koQvKRfXR710MVn+P1DULBLL0CMjR4K/gRl6QM0Tbhx0uH34aAL76ojmP5tL5; BASKET_REF=0d847f11-0438-4dfe-8e6b-0f781f9d6fac; FEATURE=99; OCADOSESSIONID=93C05108A8923269E24079DDC9A1D613D4985AB9; OVQC=T; TS011f4592=01ca839169f58c2fe322884e72e231eb5670c21dbe9fdc1f21f5e8dd67548382dd21a745dbdb270cf1fbc6eaf59e38519b6353d7bf; TS01e00a79=015956ddf6e1f8ba51b32573a205f17a6b9a8a533b6e4ceaa4682e92313f896086c8710865efce6e00390f3ffe77eb8d4a0b84d315; XSRF-TOKEN=8c4b7afb-035b-465a-843a-7273a6caf12c; __qca=P0-1254195758-1718795693925; __utmzz=utmcsr=google|utmcmd=organic|utmccn=(not set)|utmctr=(not provided); __utmzzses=1; _cs_c=0; _cs_id=6588de2d-74a4-afd5-f688-c9657d95e1b8.1718795693.2.1718802123.1718802114.1.1752959693223.1; _dy_c_att_exps=; _dy_c_exps=; _dy_csc_ses=7f7k72ugf9wqwrb6g5dkw5heuzpu5kgt; _dy_df_geo=Poland..Warsaw; _dy_geo=PL.EU.PL_14.PL_14_Warsaw; _dy_soct=1087145.1250751.1718795694.7f7k72ugf9wqwrb6g5dkw5heuzpu5kgt*1087147.1250753.1718795694.7f7k72ugf9wqwrb6g5dkw5heuzpu5kgt*1191745.1536763.1718795694.7f7k72ugf9wqwrb6g5dkw5heuzpu5kgt*1208754.1647152.1718795694*1236149.1842451.1718795694; _dy_toffset=0; _dycnst=dg; _dycst=dk.m.c.ws.; _dyid=4773816568804118325; _dyid_server=4773816568804118325; _dyjsession=7f7k72ugf9wqwrb6g5dkw5heuzpu5kgt; _fbp=fb.1.1718616031859.996661741816349054; _ga=GA1.2.625998487.1718616032; _ga_12345=GS1.1.1718802114.3.1.1718802129.0.0.0; _ga_HWE61G3QP0=GS1.1.1718780285.3.1.1718812053.33.0.1247067104; _ga_JSWXTLWJ1X=GS1.1.1718802114.3.1.1718802129.0.0.0; _gcl_au=1.1.607146736.1718616032; _gid=GA1.2.815753836.1718616032; _pin_unauth=dWlkPU0yTmlPR1E0TXpZdE5EZ3pPUzAwTmpnMkxUZzJNVEl0WTJabU1tTmhNelZsT0RCaQ; _scid=9a875704-716b-419a-ae14-5f3d1f92fbdc; _scid_r=9a875704-716b-419a-ae14-5f3d1f92fbdc; _uetsid=d8532ae02c8a11efa4e885122131c1a1; _uetvid=d85349502c8a11efad7923ab93ca8cf7; dy_cookies_accepted=true; dy_fs_page=alpha.sit.zoom.ocado.com%2Fundeliverable-address; postcode-checked=; sessionid=NTIwOGIxNTQtY2Q0MS00ZDY5LTk5OGItZjVhMmZiYTIxZDkx; shopversion=v2;user-session-id=s%3AS4AtZfQrX6jfzJwja2u5kYAM4GrD9TPq.dQhBC%2F7Jn5ZJ4kHeYg18LDo8O940XNDxKZyq4Jcuhak",
                                        "accept-language": "uk,en;q=0.9,es;q=0.8,ru;q=0.7,pl;q=0.6",
                                        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
                                        "accept-encoding": "gzip, deflate, br, zstd",
                                        "sec-ch-ua": "\"Google Chrome\";v=\"125\", \"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\"",
                                        "sec-ch-ua-mobile": "?0",
                                        "sec-ch-ua-platform": "\"macOS\"",
                                        "upgrade-insecure-requests": "1",
                                        "sec-fetch-site": "none",
                                        "sec-fetch-mode": "navigate",
                                        "sec-fetch-user": "?1",
                                        "sec-fetch-dest": "document",
                                        "priority": "u=0, i",
                                        "cloudfront-is-mobile-viewer": "false",
                                        "cloudfront-is-tablet-viewer": "false",
                                        "cloudfront-is-smarttv-viewer": "false",
                                        "cloudfront-is-desktop-viewer": "true",
                                        "cloudfront-viewer-country": "PL",
                                        "cloudfront-forwarded-proto": "https",
                                        "cloudfront-viewer-asn": "6830",
                                        "request-id": "4e62a663-5876-4968-ae86-25d96bfd7aa7",
                                        "Cache-Control": "no-cache",
                                        "Host": "localhost"
                                    },

                    }, ...etc

So you can see here this line: "x-forwarded-for": "213.134.162.194, 15.158.16.106, 10.195.255.53",

Is there some way to remove it from next.config or before rendering the page and receiving its data? As we do not have any other pages that are receiving such headers in nextscript tag (its rendered in _document.tsx)