Major fix for a race condition vulnerability

This commit is contained in:
Yessiest 2024-04-27 22:33:40 +04:00
parent e251d2def6
commit d132b6a2a1
8 changed files with 71 additions and 28 deletions

View File

@ -12,7 +12,7 @@ app = Landline::Server.new do
end end
path "important" do path "important" do
preprocess do |req| preprocess do |req|
puts "This is a context for #{request}" if defined? request
# Implement logging logic here # Implement logging logic here
puts "Client at #{req.headers['remote-addr']} wanted to access something /important!" puts "Client at #{req.headers['remote-addr']} wanted to access something /important!"
end end

View File

@ -8,23 +8,21 @@ Landline::Session.hmac_secret = "Your secure signing secret here"
app = Landline::Server.new do app = Landline::Server.new do
get "/make_cookie" do get "/make_cookie" do
session["random_number"] = Random.random_number(100) session["random_number"] = Random.random_number(100)
text = <<~HTML <<~HTML
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title> <title>
Session test #{request.to_s} Session test
</title> </title>
</head> </head>
<body> <body>
<h1>Your random number is #RAND#!</h1> <h1>Your random number has been created!</h1>
<hr> <hr>
<p>Go check it at <a href="/check_cookie">this link!</a></p> <p>Go check it at <a href="/check_cookie">this link!</a></p>
</body> </body>
</html> </html>
HTML HTML
sleep(20)
text.gsub("#RAND#", request.to_s)
end end
get "/check_cookie" do get "/check_cookie" do

View File

@ -1 +0,0 @@
../../lib

View File

@ -22,6 +22,10 @@ module Landline
@hmac_secret ||= ENV.fetch('HMAC_SECRET', SecureRandom.base64(80)) @hmac_secret ||= ENV.fetch('HMAC_SECRET', SecureRandom.base64(80))
end end
# Class for representing session errors
class SessionError < ::StandardError
end
# Class for representing session storage # Class for representing session storage
class Session class Session
def initialize(cookie, cookies_callback) def initialize(cookie, cookies_callback)
@ -40,7 +44,7 @@ module Landline
# Retrieve data from session storage # Retrieve data from session storage
# @param key [String, Symbol] serializable key # @param key [String, Symbol] serializable key
def [](key) def [](key)
raise StandardError, "session not valid" unless @valid raise Landline::Session::SessionError, "session not valid" unless @valid
unless key.is_a? String or key.is_a? Symbol unless key.is_a? String or key.is_a? Symbol
raise StandardError, "key not serializable" raise StandardError, "key not serializable"
@ -53,7 +57,7 @@ module Landline
# @param key [String, Symbol] serializable key # @param key [String, Symbol] serializable key
# @param value [Object] serializable data # @param value [Object] serializable data
def []=(key, value) def []=(key, value)
raise StandardError, "session not valid" unless @valid raise Landline::Session::SessionError, "session not valid" unless @valid
unless key.is_a? String or key.is_a? Symbol unless key.is_a? String or key.is_a? Symbol
raise StandardError, "key not serializable" raise StandardError, "key not serializable"

View File

@ -31,8 +31,20 @@ module Landline
end end
end end
# Ephemeral proxy class to which callback execution binds
class PathExecutionOrigin
def initialize(request, properties)
@request = request
@properties = Landline::Util::LookupROProxy.new(properties)
end
attr_accessor :response
attr_reader :request, :properties
end
# Primary building block of request navigation. # Primary building block of request navigation.
class Path < Landline::Node class Path < Landline::Node
ExecutionOrigin = Landline::PathExecutionOrigin
ProcContext = Landline::ProcessorContext ProcContext = Landline::ProcessorContext
Context = Landline::PathContext Context = Landline::PathContext
@ -50,8 +62,6 @@ module Landline
# Contexts setup # Contexts setup
context = self.class::Context.new(self) context = self.class::Context.new(self)
context.instance_exec(&setup) context.instance_exec(&setup)
# TODO: This isn't fine
@proccontext = self.class::ProcContext.new(self)
end end
# Method callback on successful request navigation. # Method callback on successful request navigation.
@ -96,12 +106,19 @@ module Landline
private private
# Create an execution context for in-path processing blocks
def get_context(request)
exec_origin = self.class::ExecutionOrigin.new(request, @properties)
self.class::ProcContext.new(exec_origin)
end
# Sequentially run through all filters and drop request if one is false # Sequentially run through all filters and drop request if one is false
# @param request [Landline::Request] # @param request [Landline::Request]
# @return [Boolean] true if request passed all filters # @return [Boolean] true if request passed all filters
def run_filters(request) def run_filters(request)
proccontext = get_context(request)
@filters.each do |filter| @filters.each do |filter|
return false unless @proccontext.instance_exec(request, &filter) return false unless proccontext.instance_exec(request, &filter)
end end
true true
end end
@ -109,8 +126,9 @@ module Landline
# Sequentially run all preprocessors on a request # Sequentially run all preprocessors on a request
# @param request [Landline::Request] # @param request [Landline::Request]
def run_preprocessors(request) def run_preprocessors(request)
proccontext = get_context(request)
@preprocessors.each do |preproc| @preprocessors.each do |preproc|
@proccontext.instance_exec(request, &preproc) proccontext.instance_exec(request, &preproc)
end end
end end
@ -126,7 +144,6 @@ module Landline
# @return [Boolean] true if further navigation will be done # @return [Boolean] true if further navigation will be done
# @raise [UncaughtThrowError] by default throws :response if no matches found. # @raise [UncaughtThrowError] by default throws :response if no matches found.
def process_wrapped(request) def process_wrapped(request)
@request = request
return false unless run_filters(request) return false unless run_filters(request)
run_preprocessors(request) run_preprocessors(request)
@ -149,7 +166,7 @@ module Landline
# @param request [Landline::Request] # @param request [Landline::Request]
def exit_stack(request, response = nil) def exit_stack(request, response = nil)
request.run_postprocessors(response) request.run_postprocessors(response)
false response
end end
# Try to perform indexing on the path if possible # Try to perform indexing on the path if possible
@ -174,8 +191,9 @@ module Landline
# @param backtrace [Array(String), nil] # @param backtrace [Array(String), nil]
# @raise [UncaughtThrowError] throws :finish to stop processing # @raise [UncaughtThrowError] throws :finish to stop processing
def _die(errorcode, backtrace: nil) def _die(errorcode, backtrace: nil)
proccontext = get_context(request)
throw :finish, [errorcode].append( throw :finish, [errorcode].append(
*@proccontext.instance_exec( *proccontext.instance_exec(
errorcode, errorcode,
backtrace: backtrace, backtrace: backtrace,
&(@properties["handle.#{errorcode}"] or &(@properties["handle.#{errorcode}"] or

View File

@ -35,6 +35,17 @@ module Landline
end end
end end
# Ephemeral proxy class to which callback execution binds
class ProbeExecutionOrigin
def initialize(request, properties)
@request = request
@properties = Landline::Util::LookupROProxy.new(properties)
end
attr_accessor :response
attr_reader :request, :properties
end
# Test probe. Also base for all "reactive" nodes. # Test probe. Also base for all "reactive" nodes.
class Probe < Landline::Node class Probe < Landline::Node
# @param path [Object] # @param path [Object]

View File

@ -12,8 +12,6 @@ module Landline
def initialize(path, **args, &exec) def initialize(path, **args, &exec)
super(path, **args) super(path, **args)
@callback = exec @callback = exec
@context = Landline::ProbeContext.new(self)
@response = nil
end end
attr_accessor :response attr_accessor :response
@ -34,20 +32,21 @@ module Landline
# @return [Boolean] true if further navigation is possible # @return [Boolean] true if further navigation is possible
# @raise [UncaughtThrowError] may raise if die() is called. # @raise [UncaughtThrowError] may raise if die() is called.
def process(request) def process(request)
@response = nil origin = Landline::ProbeExecutionOrigin.new(request, @properties)
context = Landline::ProbeContext.new(origin)
return reject(request) unless request.path.match?(/^\/?$/) return reject(request) unless request.path.match?(/^\/?$/)
@request = request
response = catch(:break) do response = catch(:break) do
@context.instance_exec(*request.splat, context.instance_exec(*request.splat,
**request.param, **request.param,
&@callback) &@callback)
end end
return false unless response return false unless response
if @response and [String, File, IO].include? response.class oresponse = origin.response
@response.body = response if oresponse and [String, File, IO].include? response.class
throw :finish, @response oresponse.body = response
throw :finish, oresponse
end end
throw :finish, response throw :finish, response
end end

View File

@ -33,5 +33,19 @@ module Landline
attr_accessor :parent attr_accessor :parent
end end
# Read-only lookup proxy
class LookupROProxy
def initialize(lookup)
@lookup = lookup
end
# Get a value by key
# @param key [#hash] key for value
# @return [Object,nil]
def [](key)
@lookup.[](key)
end
end
end end
end end