Major fix for a race condition vulnerability
This commit is contained in:
parent
e251d2def6
commit
d132b6a2a1
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../lib
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue