Anti aliasing in ruby attribute assignment and a TDD session
Since a week far away I’m on vacation at Guardavalle Marina, in the very Southern of Italy. Here I’m relaxing trying to fixing up some tool I use as application security specialist at work.
I was working on cross and I faced a strange aliasing bug while playing with an attribute assignment.
The TDD workout
cross has very basic features. If you want to inject cross site scripting attack patterns in the web application URL parameters, you can’t choose the parameter to inject. This is very limiting.
Using Owasp WebGoat as example, from a url obtained with an auxiliary parser, starting from an url like the following:
http://localhost:8080/WebGoat/attack?Screen=130&menu=900
I would like to be able to say cross to inject attack vettors only on Screen parameter leaving the other untouched.
This is the spec file I wrote.
``` ruby url_spec.rb require ‘spec_helper’
describe “Url fuzzer” do let (:url) {Cross::Url.new(“http://localhost:8080/WebGoat/attack?Screen=130&menu=900”)} it “will recognize the param list” do url.params.class.should == Array url.params.size.should == 2 end
it “will recognize parameter names” do url.params[0][:name].should == ‘Screen’ url.params[1][:name].should == ‘menu’ end
it “will recognize parameter values” do url.params[0][:value].should == “130” url.params[1][:value].should == “900” end
it “will provide a get shortcut for getting parameters value” do url.get(“Screen”).should == “130” url.get(“menu”).should == “900” end
it “will handle errors smoothly” do url.get(“nonexistent”).should be_nil end
it “will make a copy of parameters” do url.original_params.should == url.params end
it “will make params Array to be reverted as string” do url.params_to_url.should == “Screen=130&menu=900” end
describe “will provide an handy set shortcut that” do it “sets an existing params to a given value” do url.set(“Screen”, “123”) url.get(“Screen”).should == “123” end
it "handle the error condition smootly" do
url.set("nonexistent", false)
url.original_params.should == url.params
end
it "won't change the original params" do
url.set("Screen", "123")
url.original_params.should_not == url.params
end end it "will fuzz" do
url.fuzz("Screen", "12").should == "http://localhost:8080/WebGoat/attack?Screen=12&menu=900"
url.fuzz("Screen", "afuzztest").should == "http://localhost:8080/WebGoat/attack?Screen=afuzztest&menu=900"
url.fuzz("menu", "11").should == "http://localhost:8080/WebGoat/attack?Screen=afuzztest&menu=11" end
it “will fuzz honoring original params if requested” do url.fuzz(“Screen”, “12”).should == “http://localhost:8080/WebGoat/attack?Screen=12&menu=900” url.fuzz(“Screen”, “afuzztest”).should == “http://localhost:8080/WebGoat/attack?Screen=afuzztest&menu=900” url.reset url.fuzz(“menu”, “11”).should == “http://localhost:8080/WebGoat/attack?Screen=130&menu=11”
end end
Pretty easy, isn't it?!? This is the Cross::Url class I wrote to fit my needs.
As usual it's shorter the code than the tests even counting the whitespaces
(actually this code make all the tests to pass, let's go into the post seeing
the original class implementation).
``` ruby Cross::Url class
module Cross
class Url
attr_reader :url
attr_reader :base_url
attr_reader :params
attr_reader :original_params
def initialize(url)
@url = url
@params = []
@original_params = []
@base_url = url.split('?')[0]
p_array = url.split('?')[1].split('&')
p_array.each do |p|
pp = p.split('=')
param = {}
param[:name] = pp[0]
param[:value] = pp[1] unless pp[1].nil?
@params << param
@original_params << param.dup
end
@original_params.freeze
end
def fuzz(name, value)
set(name, value)
"#{@base_url}?#{params_to_url}"
end
def get(name)
value = nil
@params.each do |p|
value = p[:value] if p[:name] == name
end
value
end
def set(name, value)
@params.each do |p|
p[:value] = value if p[:name] == name
end
end
def reset
@params = @original_params
end
def params_to_url
ret = ""
@params.each do |p|
ret += "#{p[:name]}=#{p[:value]}"
if !(p == @params.last)
ret +="&"
end
end
ret
end
end
end
The antialias bug
Running the specs, results disappointed me.
It seemed to me I was encountering an alias bug for the @original_params class attribute.
What I was looking for was to save the original parameter values in an Array I eventually can use again to replay the original request.
It seems that every change I was playing over the param Array it was reflected also on original_params.
This is very weird since I made a dup for the params Array and then freezing the original_params one.
I was arguing a day about this bug… well, in the time I was not swimming or enjoying making castles in the sand with my child. I know you can understand.
``` ruby freezing the wrong object def initialize(url) @url = url @params = [] @original_params = [] @base_url = url.split(‘?’)[0] p_array = url.split(‘?’)[1].split(‘&’) p_array.each do |p| pp = p.split(‘=’) param = {} param[:name] = pp[0] param[:value] = pp[1] unless pp[1].nil?
@params << param end
@original_params = @params.dup @original_params.freeze end
> In Ruby, a variable is simply a reference to an object.
## Making the right clone
I tried so to make a copy of the variable I was putting in my "not to be
tampered" array and everything went well.
``` ruby freezing the right object
def initialize(url)
@url = url
@params = []
@original_params = []
@base_url = url.split('?')[0]
p_array = url.split('?')[1].split('&')
p_array.each do |p|
pp = p.split('=')
param = {}
param[:name] = pp[0]
param[:value] = pp[1] unless pp[1].nil?
@params << param
@original_params << param.dup
end
@original_params.freeze
end
Fuzzing and inspecting
The method I will use most in Cross::Url it will be the fuzz() method. I created this to be able to mangle the request URI injecting a Cross Site Scripting attacking parameter into a specified parameter enumerating all the possible attack scenarios.
``` ruby Cross::Url.fuzz() def fuzz(name, value) reset set(name, value) “#{@base_url}?#{params_to_url}” end
The idea is to call the fuzz method in a loop like the following one and then
attacking all the target page parameters:
``` ruby
url = Cross::Url.new("http://localhost:8080/WebGoat/attack?Screen=130&menu=900")
Cross::Attack::XSS.each do |pattern|
url.params.each do |par|
page = @agent.get(url.fuzz(par[:name], pattern))
scripts = page.search("//script")
scripts.each do |sc|
found = true if sc.children.text.include?("alert('cross canary');")
@agent.log.debug(sc.children.text) if @options[:debug]
end
end
end