{"id":"GHSA-6rv8-qj5v-p8ch","kind":"vulnerability","aliases":["GHSA-6rv8-qj5v-p8ch"],"related":[],"title":"Temporary signed URL expiration bypass via unencoded numeric-keyed route parameters","description":"### Impact\n\nWhen an application passes attacker-influenced, numeric-keyed parameters (for example a request's query array) to `temporarySignedRoute()` or `signedRoute()`, and those parameters do not fill a route placeholder, Laravel appended their values to the query string without URL encoding. An attacker can use this to add a second `expires` field to the signed URL. The signature covers that field, but request parsing reads it as the authoritative value, so the signed URL is treated as never expiring. Exploitation requires the application to forward such input into signed URL generation.\n\n### Patches\n\nThis issue has been patched in Laravel 12.72.0 and 13.41.0, which URL-encode numeric-keyed query parameters: https://github.com/laravel/framework/pull/62401. Users should upgrade to one of these versions or later.\n\n### Workarounds\n\nDo not pass request-derived arrays to signed route generation. Pass only explicitly selected, string-keyed parameters.","summary":"Laravel's signed route helpers append numeric-keyed parameters to the query string without URL encoding, so an attacker can add a second expires field. The signature covers that field, but the request parser reads it as authoritative, so the link never expires.","severity":"medium","cvss":4.8,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"laravel/framework","fixed":["12.72.0"],"affected":"< 12.72.0","ecosystem":"composer"},{"name":"laravel/framework","fixed":["13.41.0"],"affected":">= 13.0.0, < 13.41.0","ecosystem":"composer"}],"ecosystems":["composer"],"tags":["composer","laravel","package","php","signed-urls"],"not_affected_if":["static-parameters","string-keyed-params-only"],"how_to_check":"- Check the installed version with `composer show laravel/framework`\n- Search the code for `temporarySignedRoute(` or `signedRoute(` calls that receive request-derived arrays, such as `$request->query()`","fix":"- `laravel/framework` below `12.72.0` on the 12.x line: move to `12.72.0`\n- `laravel/framework` from `13.0.0` below `13.41.0` on the 13.x line: move to `13.41.0`","status":"open","published_at":"2026-11-11T13:51:58+00:00","first_seen_at":"2026-11-11T14:01:45+00:00","triaged_at":"2026-11-11T14:02:13+00:00","updated_at":"2026-11-11T14:02:13+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-11T14:02:13Z","model":"claude-haiku-5-5","output":{"fix":"- `laravel/framework` below `12.72.0` on the 12.x line: move to `12.72.0`\n- `laravel/framework` from `13.0.0` below `13.41.0` on the 13.x line: move to `13.41.0`","reach":"internet","title":"Laravel: signed URLs can be made to never expire via query injection","impact":"medium","summary":"Laravel's signed route helpers append numeric-keyed parameters to the query string without URL encoding, so an attacker can add a second expires field. The signature covers that field, but the request parser reads it as authoritative, so the link never expires.","audience":"developers","category":"package","nicknames":[],"who_cares":["laravel","php","composer","signed-urls"],"confidence":0.85,"fix_status":"available","needs_auth":"no","worst_case":"signed link stays valid forever, bypassing expiry","analyst_note":"","how_to_check":"- Check the installed version with `composer show laravel/framework`\n- Search the code for `temporarySignedRoute(` or `signedRoute(` calls that receive request-derived arrays, such as `$request->query()`","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["static-parameters","string-keyed-params-only"],"who_is_affected":"Only applications that pass attacker-influenced, numeric-keyed input (for example a request's query array) into signed URL generation where those values do not fill a route placeholder. Applications that pass only explicitly selected, string-keyed parameters are not exposed.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000764,"input_tokens":4874,"output_tokens":553,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'/laravel/framework/pull/62401.':191C '12.72.0':177C '13.41.0':179C '6rv8':15A,20A 'add':53B,125C 'append':37B,109C 'applic':79C,160C 'array':94C,209C 'attack':51B,82C,120C 'attacker-influenc':81C 'authorit':70B,146C 'bypass':5A 'compos':25B 'cover':60B,136C 'deriv':208C 'encod':48B,118C,183C 'exampl':89C 'expir':4A,56B,75B,128C,156C 'explicit':216C 'exploit':157C 'field':57B,62B,129C,138C 'fill':104C 'forward':162C 'generat':168C,213C 'ghsa':14A,19A 'ghsa-6rv8-qj5v-p8ch':13A,18A 'github.com':190C 'github.com/laravel/framework/pull/62401.':189C 'helper':36B 'impact':76C 'influenc':83C 'input':164C 'issu':171C 'key':10A,40B,86C,186C,220C 'laravel':26B,32B,108C,176C 'laravel/framework':23A,24A 'later':201C 'link':73B 'never':74B,155C 'numer':9A,39B,85C,185C 'numeric-key':8A,38B,84C,184C 'one':196C 'p8ch':17A,22A 'packag':27B 'paramet':12A,41B,87C,101C,188C,221C 'pars':141C 'parser':66B 'pass':80C,205C,214C 'patch':169C,174C 'php':28B 'placehold':107C 'qj5v':16A,21A 'queri':44B,93C,114C,187C 'read':67B,142C 'request':65B,91C,140C,207C 'request-deriv':206C 'requir':158C 'rout':11A,35B,106C,212C 'second':55B,127C 'select':217C 'sign':2A,30B,34B,132C,150C,166C,211C 'signatur':59B,135C 'signed-url':29B 'signedrout':98C 'string':45B,115C,219C 'string-key':218C 'temporari':1A 'temporarysignedrout':96C 'treat':153C 'unencod':7A 'upgrad':194C 'url':3A,31B,47B,117C,133C,151C,167C,182C 'url-encod':181C 'use':122C 'user':192C 'valu':111C,147C 'version':199C 'via':6A 'without':46B,116C 'workaround':202C","nicknames":[],"mitigations":[],"who_affected":"Only applications that pass attacker-influenced, numeric-keyed input (for example a request's query array) into signed URL generation where those values do not fill a route placeholder. Applications that pass only explicitly selected, string-keyed parameters are not exposed.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["laravelframework"],"ptokens":["framework","laravel"],"labels":["composer","laravel","medium","package","php","signedurls"],"campaign_type":null,"search_a":"'6rv8':15A,20A 'bypass':5A 'expir':4A 'ghsa':14A,19A 'ghsa-6rv8-qj5v-p8ch':13A,18A 'key':10A 'laravel/framework':23A,24A 'numer':9A 'numeric-key':8A 'p8ch':17A,22A 'paramet':12A 'qj5v':16A,21A 'rout':11A 'sign':2A 'temporari':1A 'unencod':7A 'url':3A 'via':6A","search_b":"'add':53B 'append':37B 'attack':51B 'authorit':70B 'compos':25B 'cover':60B 'encod':48B 'expir':56B,75B 'field':57B,62B 'helper':36B 'key':40B 'laravel':26B,32B 'link':73B 'never':74B 'numer':39B 'numeric-key':38B 'packag':27B 'paramet':41B 'parser':66B 'php':28B 'queri':44B 'read':67B 'request':65B 'rout':35B 'second':55B 'sign':30B,34B 'signatur':59B 'signed-url':29B 'string':45B 'url':31B,47B 'without':46B","search_c":"'/laravel/framework/pull/62401.':191C '12.72.0':177C '13.41.0':179C 'add':125C 'append':109C 'applic':79C,160C 'array':94C,209C 'attack':82C,120C 'attacker-influenc':81C 'authorit':146C 'cover':136C 'deriv':208C 'encod':118C,183C 'exampl':89C 'expir':128C,156C 'explicit':216C 'exploit':157C 'field':129C,138C 'fill':104C 'forward':162C 'generat':168C,213C 'github.com':190C 'github.com/laravel/framework/pull/62401.':189C 'impact':76C 'influenc':83C 'input':164C 'issu':171C 'key':86C,186C,220C 'laravel':108C,176C 'later':201C 'never':155C 'numer':85C,185C 'numeric-key':84C,184C 'one':196C 'paramet':87C,101C,188C,221C 'pars':141C 'pass':80C,205C,214C 'patch':169C,174C 'placehold':107C 'queri':93C,114C,187C 'read':142C 'request':91C,140C,207C 'request-deriv':206C 'requir':158C 'rout':106C,212C 'second':127C 'select':217C 'sign':132C,150C,166C,211C 'signatur':135C 'signedrout':98C 'string':115C,219C 'string-key':218C 'temporarysignedrout':96C 'treat':153C 'upgrad':194C 'url':117C,133C,151C,167C,182C 'url-encod':181C 'use':122C 'user':192C 'valu':111C,147C 'version':199C 'without':116C 'workaround':202C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-11T13:51:58+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-6rv8-qj5v-p8ch","url":"https://github.com/laravel/framework/security/advisories/GHSA-6rv8-qj5v-p8ch","severity":"medium","published_at":"2026-11-11T13:51:58+00:00","modified_at":"2026-11-11T13:51:58+00:00","first_seen_at":"2026-11-11T14:01:45+00:00","fetched_at":"2026-11-11T14:01:45+00:00","note":null}],"documents":[{"id":126033,"url":"https://github.com/laravel/framework/security/advisories/GHSA-6rv8-qj5v-p8ch","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}