Don’t iterate to convert built-in Ruby data types

Both the Ruby language and the Ruby on Rails framework provide a lot of convenience methods compared to other technology stacks, improving ease of use and developer productivity. However, it’s difficult when starting out with Ruby to know what ease-of-use functions are available to you as a programmer.

Here’s a heuristic to help with that: if you’re writing a Ruby program, and you find yourself writing code that uses iteration to convert one built-in data type to another, there’s probably a language feature that could simplify your code. Let’s go through a few examples of this that I’ve encountered since I started coding in Ruby.

Generating an array of numbers between 1 and N

Suppose you want to generate an ordered array of integers between 1 and N. We might be tempted to use the .times method on our integer N, convert that enumerator to an array, then map each number to be increased by 1 (since .times provides us numbers from 0 to N – 1). For example, if N is 5, we can use the following code:

5.times.to_a.map{|n| n + 1}
=> [1, 2, 3, 4, 5] 

This works, but Ruby provides simpler ways for us to achieve the same goal using upto or a Range:

1.upto(5).to_a
 => [1, 2, 3, 4, 5] 

(1..5).to_a
 => [1, 2, 3, 4, 5] 

Convert the values of a hash into an array

Suppose you want an array containing the values of a Hash, and don’t need to know what the keys are. We might be tempted to call .map on the Hash, and then return the value in that block:

{foo: 1, bar: 2, car: 3}.map{ |_key, value| value }
 => [1, 2, 3] 

With far fewer keystrokes, .values lets us do the same thing:

{foo: 1, bar: 2, car: 3}.values
 => [1, 2, 3] 

(Of course, if we’re interested in the keys and not the values, Ruby provides a .keys method as well.)

Combining two hashes

Suppose have two Hash variables, h0 and h1, and want to create a new Hash containing the keys and values in both of them. In the case of duplicate keys between h0 and h1, we’ll take the value from h1 (this is arbitrary; using the value from h0 would be valid too). We could write a method to iterate over two hashes like so:

However, the far simpler approach is to use the .merge method provided by Ruby:

{foo: 1, bar: 2}.merge({bar: 3, car: 4})
 => {:foo=>1, :bar=>3, :car=>4} 

Conclusion

Ruby (and Ruby on Rails) provides us with a lot more convenience methods than other popular languages like Go and JavaScript. Learning all of them requires reading the language documentation, along with simply practicing with the language over time. However, these convenience methods aren’t created at random; there are patterns in them that we can identify to become more effective Ruby developers.

Start your journey towards writing better software, and watch this space for new content.

Use RSpec to improve performance via precomputation

Suppose that we have a Ruby class called SomeClass, and SomeClass has an array class constant called SOME_ARRAY. SomeClass also has a method called same_elements? that checks if an input array has the same elements as SOME_ARRAY without regard to ordering. We may be tempted to write code that looks like this:

This code works, but it’s an inefficient solution. We sort SOME_ARRAY every time same_elements? is called, even though it’s a constant that we defined ourselves!

We could define SOME_ARRAY as ['foo', 'bar', 'car'].sort, and that gets us most of the way to improved performance. We only expect Rails to load our class definition for SomeClass once when we start our application, so even if we’re creating many instances of SomeClass (eg. once per request to a REST endpoint), we’re still only calling .sort on SOME_ARRAY once.

However, if we want to be even more efficient with our compute power at runtime, we can simply define SOME_ARRAY to be sorted when we write our code, which lets us avoid calling .sort when we load the class definition.

This revised code has even fewer calls to .sort than when we defined SOME_ARRAY as as ['foo', 'bar', 'car'].sort. However, this introduces a new problem into our code: if we ever rewrite or modify SOME_ARRAY in the future, we need to ensure that that constant remains sorted.

For example, a future commit could change SOME_ARRAY to ['new element', 'bar', 'car', 'foo'], which would cause a bug in our same_elements? method. Hopefully our code review process and tests on same_elements? would stop us from introducing that bug.

However, through RSpec, we can actually enforce that SOME_ARRAY remains sorted. We introduce a test like so into our SomeClass test suite:

This ensures that SOME_ARRAY remains ordered when we introduce new elements to it. By precomputing the sorted order of SOME_ARRAY in our testing and continuous integration environments, we’re able to do fewer .sort computations at runtime in our application.

Note that we should only use this method when there’s no impact to SOME_ARRAY‘s readability. If SOME_ARRAY is defined as ['foo', 'bar', 'car'] (ie. random elements where we don’t care about the ordering), making its definition alphabetical in the source code makes sense. Using this method also makes sense when SOME_ARRAY is something we already expect to be alphabetized (eg. ['alpha', 'bravo', 'charlie']). However, if we expect some other sort of non-alphabetized ordering of SOME_ARRAY, then making our code less readable in order to save compute power would be the wrong tradeoff. For example, redefining ['one', 'two, 'three'] to the sorted ['one', three', 'two'] makes our code less readable, which isn’t worth the slight performance gains.

Start your journey towards writing better software, and watch this space for new content.

This is how percentages work in Flipper

Flipper is a Ruby library that implements feature flags. We use it to turn features on and off via configuration data that our application reads at runtime, meaning we can enable and disable features without modifying our actual Ruby code. Flipper has the concept of a Gate to determine whether or not a feature is enabled – for example, the simplest Gate is a boolean that globally determines whether a feature is turned on/off for the whole application. However, Flipper also lets us turn a feature on/off for a certain percentage of actors. Within this blog post, I’ll treat a “user” and “actor” as being interchangeable concepts.

If we want to gradually roll out a new feature to our application’s users, we may start by only enabling a Flipper Gate for 1% of users – then gradually increase that percentage to 2%, then 3%, and so on until we reach 100%.

This raises multiple questions, though:

  • Which 1% of users do we enable this feature for? Are these users selected randomly or deterministically?
  • When we increase that percentage from 1% to 2%, are the users from the 1% cohort a subset of the 2% cohort? We’d hope that Flipper is designed this way, since we don’t want a user to see a new feature in our application toggled on/off during a gradual rollout.
  • Are we introducing additional state to a user when a feature is enabled for them?

Let’s find out by reading the percentage_of_actors.rb file in the Flipper source code. open? looks like a promising method to read in that file:

We can avoid diving deeply into how Types::Actor.wrappable? works, since it’s an internal implementation detail of Flipper that is outside of the scope of this blog post. We can assume that actor.value returns the unique flipper_id that we see examples of in Flipper’s documentation. For example, if we’re dealing with a feature flag called foo_enabled for a user with an ID of 123, actor.value should be "User;#123", meaning the local variable id in the above snippet is "foo_enabledUser;123".

We then calculate the CRC32 of that id. CRC32 is an algorithm designed to check data integrity, but it works pretty well as a hash function if we’re not using it in a security-related context. This means that the output of CRC32 is pseudorandom and fairly uniformly distributed, even though it’s calculated deterministically. So, it should look like the users who have a feature enabled were selected at random, even though their eligibility is deterministic.

The inequality below (taken from the code snippet) will tell us whether or not cohorts for smaller percentages will be subsets of cohorts for larger percentages:

Zlib.crc32(id) % (100 * scaling_factor) < percentage * scaling_factor

percentage is the variable that we defined to be strictly increasing from 1% to 100%. scaling_factor is hardcoded to 1000, meaning percentage * scaling_factor is strictly increasing as well. Ruby’s implementation of CRC32 also interprets its output as an unsigned (ie. positive) integer. This means that all of the IDs for whom the inequality held true with a given percentage will also hold true for a larger percentage.

Let’s use an example: if percentage is 1, then any users where Zlib.crc32(id) % (100 * scaling_factor) is less than 1000 will have the foo_enabled feature turned on for them. Roughly 99% of our users will have a value of over 1000 in this check, so this feature will be turned off for 99% of users. However, for user 24 (ie. with id value set to "foo_enabledUser;24“, the CRC of their ID is 278800337, and 278800337 % (100*scaling_factor) returns 337.

The key point here is that this will always be 337 for user 24, regardless of what our percentage is set to! 337 is less than 1000, and if we strictly increase percentage to values such as 2 and 3, this inequality also holds true for their corresponding values for percentage * scaling_factor (such as 2000 and 3000).

To conclude:

  • We can expect our users to be sampled more-or-less at random by Flipper, even though their eligibility for a feature is calculated deterministically via CRC32. This is because CRC32 can be used as a hash function in this context.
  • If we’re strictly increasing our percentage of users enabled, then all users from the cohort of smaller percentages will also be included in the cohorts of larger percentages too.
  • Calculating a user’s eligibility for a feature via CRC32 doesn’t add additional state to the user.

Start your journey towards writing better software, and watch this space for new content.

Gem::Version != SemVer

Using dependencies with semantic versioning (also known as SemVer) is a key piece of modern software development, and if we’re writing Ruby code that checks the version of a dependency, we often use Gem::Version for our operations.

The initializer for Gem::Version checks if we’ve passed in a valid version number (such as 1.9.10), and raises an ArgumentError if it’s invalid:

$ irb
3.0.0 :001 > Gem::Version.new('1.9.10')
 => #<Gem::Version "1.9.10"> 
3.0.0 :002 > Gem::Version.new('foo bar car')
ArgumentError (Malformed version number string foo bar car)

If we wanted to test this behaviour in RSpec, it would look something like so:

Most dependencies use release numbers with the MAJOR.MINOR.PATCH pattern (eg. 1.9.10), but the SemVer allows more complex identifiers than that. For a list of interesting edge cases that illustrate what is and isn’t allowed, the SemVer specification links to a Regex101 page containing a list of test strings.

Let’s pass those into Gem::Version via RSpec and see what happens! In the code below, we pass in each valid test string into the initializer for a new Gem::Version, and check that it successfully creates a Gem::Version (ie. doesn’t raise an error).

We actually receive an ArgumentError if we pass in a version number with metadata (ie. everything after the + sign), so we strip that out before passing that in. It would be ideal if Gem::Version did that metadata parsing for us, but ignoring that edge case, all of our test cases pass:

What happens when we look at the invalid test cases? Let’s look at the RSpec below:

We expect to receive an ArgumentError if we’re passed in an invalid version number, which we check for in our expect statements. We also skip some of our test cases with this snippet:

.reject { |version_string| version_string.include?('+') }

We skip these cases because we already know that Gem::Version doesn’t handle metadata correctly. Let’s run the above RSpec tests:

We see that a lot of version identifiers that are invalid under the SemVer specification don’t actually raise errors in these tests, and can be used to instantiate a Gem::Version. For example, Gem::Version.new('1.2.3.DEV') creates a new object and doesn’t raise an error, even though that’s not a valid version number under SemVer.

As it turns out, Gem::Version (ie. the versioning used by RubyGems) is much more permissive than the SemVer specification. This means that if we’re trying to adhere to the strictest definition of a SemVer version number, we can’t actually use Gem::Version to determine if a version string is valid due to this permissiveness. We can only use Gem::Version to tell us if a version string is invalid (barring the aforementioned exception around metadata and the + sign).

My favourite behaviour of Gem::Version isn’t actually covered in the negative test cases of the SemVer spec. Gem::Version lets us be arbitrarily granular with the number of dots in our version number. This isn’t valid in the SemVer specification either, but Gem::Version will happily parse it and do version comparisons with it:

Start your journey towards writing better software, and watch this space for new content.

Use RSpec to test for more than just correctness

RSpec is typically used to test the correctness of our Ruby on Rails code, but did you know that we can use it to maintain the readability of our application’s configuration files?

Suppose we have a YAML file containing an alphabetized list of all of our application’s feature flags. It might look something like so:

While our application may function correctly if feature_flags.yml stores these flags in a random order, we usually prefer an alphabetized list of flags for the sake of readability. One approach is to leave a comment for the next developer who modifies this file:

This is adequate, and hopefully our code review process would prevent a developer from adding an adding an unalphabetized flag. But, through the power of RSpec, we can actually enforce this alphabetization (rather than just suggest it with a comment!) Consider the following RSpec test that we could add to our codebase:

If another developer tried to submit a pull request containing a non-alphabetized YAML property, this test would fail the next time our tests ran in our continuous integration system. This allows us to preserve the alphabetization of this configuration file – even if the next developer to add a feature flag doesn’t read our comment!

Note that this test executes none of our actual Ruby application code – this is purely testing properties of our YAML file, and doesn’t deal with any of our classes that we expect to see at runtime.

Let’s look at another example! Suppose we have a configuration file that stores a list of UI elements to display, and we associate a position integer with each element (think of something akin to the acts_as_list library):

Again, our application may run correctly if our YAML file stores these elements in a random order, but we (and other developers) will have a far easier time understanding this file if these items are ordered by position. To keep these items ordered, we can add the following test:

If we try to add an element to this file that is out of order, this test will fail until we place it in the correct position.

It’s easy to think of a linter as our sole tool to ensure code quality, and that RSpec’s only role is to test correctness of our application – since this is usually the case! But, in occasional cases like this, we can use RSpec for more than just correctness, and leverage it to ensure high code quality in our codebase.

Start your journey towards writing better software, and watch this space for new content.

You may not know that Ruby’s ‘puts’ method does this

If you ran this code snippet, what would you expect the output to be? Take a moment to think about the answer before reading the following paragraph.

If you had asked me a few months ago, I likely would answered with ’{:bar=>"car"}’, the rationale being something like: “When we pass in an object to puts, to_s gets called to do string conversion, and '{:bar=>"car"}' is the string representation of the value returned by to_s. Seems reasonable, right?

When we actually run the code, we see the following output:

#<Foo:0x00007f7f7f160bb0>

Counterintuitive, right? We typically see the object’s class name and address (ie. #<Foo:0x00007f7f7f160bb0>) in an interpolated string like that when we haven’t explicitly defined to_s , but we did define that method.

Why are we printing out a reference to the parent Foo, rather than something related to our {:bar=>"car"} hash?

Let’s dig in further and look at a slightly different code example:

If we run the code snippet above, our terminal prints this:

#<Foo:0x00007f843b0f1268>
{:bar=>"car"}

Counterintuitive indeed. When we pass in an instance of Foo to puts, aren’t we expecting to_s to be called under the hood? Why are we getting a different result when we explicitly call to_s?

puts is Ruby function that’s purely implemented in C, so we can’t just step with a debugger like pry or byebug to find out more; puts doesn’t have Ruby code to step into! But, we can read through the Ruby source code on Github: the io.c file sounds like a promising place to read about puts, and we find this definition of rb_io_puts there:

C can be difficult to read compared to Ruby code, but this line of code looks promising: line = rb_obj_as_string(argv[i]);. So, let’s read the definition of rb_obj_as_string, which is found in the string.c file:

str = rb_funcall(obj, idTo_s, 0) calls our object’s to_s method (this idTo_type naming pattern is also found elsewhere in Ruby’s C source code for other built-in Ruby types, such as Array and Symbol). We then pass the result of to_s into rb_obj_as_string_result. How is rb_obj_as_string_result defined in string.c?

And this explains it! In the underlying implementation of puts, rb_obj_as_string_result explicitly checks if to_s has returned a string. If we haven’t returned a string, that value is overridden, and we use the return value of rb_any_to_s instead (ie. the function that returns a class name / address string like #<Foo:0x00007f7f7f160bb0>).

This is why we’re printing a reference to Foo, and not anything to do with the actual hash – the value of to_s is discarded because it’s not a string! This also explains the discrepancy between puts foo_instance and puts foo_instance.to_s – we pass in a hash to rb_obj_as_string, meaning {:bar=>"car"} is passed into rb_obj_as_string_result, which does have a definition of to_s that returns a string.

The way Ruby’s puts function to overrides a value we explicitly return with to_s can be unexpected if you haven’t seen it before, but upon reflection, I do think that this is sensible language design. The alternative would be for Ruby to recursively call our underlying rb_obj_as_string on the value returned by to_s until we get a string, but this introduces additional complexity for little benefit. At the end of the day, if we want to write clean code, any to_s functions that we write should, well, return a string 🙂

Start your journey towards writing better software, and watch this space for new content.

Bypass GitHub’s search API rate limit by 27% (with just five lines of code!)

GitHub’s search API is a powerful tool, but its search functionality is heavily rate limited compared to the rest of its API. GitHub’s general rate limit on its API is 5000 requests per hour (roughly 83 requests per minute), while the rate limit for search requests is documented at only 30 requests per minute. This can be restrictive in some use cases, but with just five lines of code, we can increase this limit to over 40 requests per minute!

(At this point, some readers may be concerned that “over 40” divided by “30” is not, in fact, an increase of 27%. Read on to find out the source of this discrepancy!)

To begin, let’s clarify those aforementioned rate limits – these are limits on requests that we’ve associated with an access token connected to our GitHub account, also known as authenticated requests. We can also query the GitHub API using unauthenticated requests (ie. without an access token), but at a much lower rate limit – GitHub only allows 10 unauthenticated search requests per minute.

However, GitHub tracks these authenticated and unauthenticated rate limits separately! This is by design, which I confirmed with GitHub via HackerOne prior to posting. To increase our effective rate limit, we can write our application code to combine our authenticated and unauthenticated API requests. Our application can make an authenticated request, and if that authenticated request fails due to rate limiting, we can retry that request again without authentication. This effectively increases our rate limit by 10 requests per minute.

Let’s illustrate with two separate code snippets –  the first using only authenticated requests, and the second using both authenticated and unauthenticated requests. In both of these snippets, we try to make 50 requests in parallel to the GitHub search API via Octokit’s search_repositories method.

In this first snippet, we expect to see 30 requests succeed (returning a Sawyer::Resource) and 20 fail (returning an Octokit error), given the documented rate limit.

Run it, and we see this output:

$ ruby authenticated_only.rb
36 requests succeeded, 14 requests failed

Oddly enough, GitHub does not appear to strictly adhere to its documented rate limit of 30 requests per minute, but our premise still holds – we can’t make all 50 requests due to GitHub’s rate limiting.

Now, let’s run the second snippet, which is five lines of code longer than our previous snippet. In this snippet, if a request using our authenticated client fails, we retry the same request using an unauthenticated client.

We see the following output:

$ ruby authenticated_and_unauthenticated.rb
46 requests succeeded, 4 requests failed

As predicted, we’ve successfully increased our rate limit from 36 to 46 requests per minute, a 27% increase from what we could achieve previously.

I really did expect to put the number 33% in this blog post’s title, not 27%. – it’s unclear to me why my authenticated client can make 36 successful requests, when the search API limit is documented at 30. I observed some variation on the output of this script too, ranging from 40 to 46 successful requests.

Going back to our performance gains – is this method effective for every application using the GitHub search API? No, probably not – 10 additional requests per minute is inconsequential in a large production application at scale. In that case, there are other techniques available to avoid hitting the GitHub search API rate limit. Some examples include caching your search results from the GitHub API, or rotating GitHub credentials to multiply your effective rate limit.

However, what if you’re using GitHub’s search API at a small scale? For example, you may be using the search API in a script that runs in your local development environment, or in some sort of internal tooling. In such a scenario, you may just be occasionally hitting the authenticated request limit, but haven’t reached a point where you need a more scalable solution. In that case, these five lines of code may give you a good “bang for your buck” in solving rate limiting issues.

Start your journey towards writing better software, and watch this space for new content.

XXEs in Golang are surprisingly hard

Go is one of my favourite programming languages, and I’ve enjoyed working with it while levelling up my skills in application security. Out of curiosity, I wanted to find out – how easily can we cause an XML External Entity (XXE) attack in a Golang application? As it turns out, doing so is surprisingly hard.

I won’t dive too deeply into how XXE attacks work, as there’s plenty of material available on that. As a quick recap, though:

  • As a language feature, XML allows us to define a key-value mapping called an entity, which our parser uses to do string substitution on instances of that entity within a document.
  • An external entity works similarly, but the parser will load content from an external URI (this includes filesystem contents and results of HTTP calls) when doing its string substitution.
  • A poorly configured XML parser will read and use an external entity definition from an untrusted input, allowing an attacker to gain access to sensitive data on the filesystem (such as /etc/passwd, or any other file the parser has permission to read).

With that, let’s begin!

Finding a working payload

Let’s start by finding a payload that we know works in another programming language’s XML parser. Take the simple Ruby script below (note that we need to explicitly enable external entities with Nokogiri::XML::ParseOptions::NOENT):

Run it and show the first ten lines of output:

$ ruby xxe.rb | head -n 10
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY users SYSTEM "file:///etc/passwd">
]>
<root>
    <child>##
# User Database
# 
# Note that this file is consulted directly only when the system is running
# in single-user mode.  At other times this information is provided by

Success! Within the <child> element, we see the header of my /etc/passwd file, so this is a working XXE payload. Let’s try using it in Go.

First attempt

Now that we’ve verified our payload in a Ruby script, let’s try doing the same thing in Go. We’ll use encoding/xml, Go’s built-in XML parsing library.

Run it, and we get… 

$ go run firstattempt.go
XML parsed into struct: {&users;}

Okay, so Decoder simply treated &users; as a string literal. That’s not what we want, but what if we tried setting Decoder.strict = true?

$ go run firstattempt.go
Error: XML syntax error on line 5: invalid character entity &users;
exit status 1

This is slightly better – we tried to parse &users; as an entity, but Decoder doesn’t recognize it as a valid entity. Why is that?

Writing to Decoder.Entity

If we peek into the docs of encoding/xml, we see that Decoder has a property called .Entity. It’s a map[string]string type that lets us define custom entities:

Let’s try setting .Entity. As an aside, this alone would make it difficult to carry out an XXE attack – a developer would have needed to explicitly set the .Entity map while coding their application – but, as we’ll see, there’s a lot more standing in an attacker’s way. We’ll modify our source code to write to .Entity:

Run our modified program:

$ go run entitymap.go 
{SYSTEM 'file:///etc/passwd'}

Okay, so Decoder did the substitution, but the file path is being treated as a string literal. Our parser isn’t fetching an external resource as we expect it to.

Reading encoding/xml’s source

The encoding/xml library is pretty small, so let’s dive into it! We’re able to find out pretty quickly while searching for entity and entities that encoding/xml doesn’t do much with our entity map. In fact, this is the only reference to it:

After this, we don’t see any calls to os.Open(), http.Get(), or anything else that would allow us to fetch an external resource. A simple string substitution is all that this library does with our .Entity map.

Confirmation via dtrace

Our source code tells us that we’re not opening /etc/passwd, but let’s double check this with dtrace! Well, I’ll be using dtruss, a similar tool for macOS. By viewing the system calls that our program makes, we should be able to tell if /etc/passwd is being read by our parser.

$ go build entitymap.go && sudo dtruss ./entitymap  2>&1 | grep passwd
XML parsed into struct: {SYSTEM 'file:///etc/passwd'}
write(0x1, "XML parsed into struct: {SYSTEM 'file:///etc/passwd'}\n\0", 0x36)            = 54 0

$ go build entitymap.go && sudo dtruss ./entitymap  2>&1 | grep open  
open("/dev/dtracehelper\0", 0x2, 0xFFFFFFFFEFBFF040)             = 3 0
open("/dev/urandom\0", 0x0, 0x0)                 = 3 0

Grepping for passwd, we just see that we write out our string literal from our entity map, and don’t actually open the file. Grepping again for open confirms this.

Conclusion

We can’t carry out an XXE attack on Golang applications using encoding/xml, since that library doesn’t handle external entities according to the XML language specification! The docs for encoding/xml describe it as “a simple XML 1.0 parser,” which sort of implies this, but I couldn’t find any docs that explicitly call out the lack of external entity processing.


It’s unclear to me whether the designers of Golang made this decision from an application security standpoint, or if they simply decided that it wouldn’t be worth the developer time to implement this XML language feature. While I find this design decision surprising, I do agree with it – the OWASP Top 10 recommends turning off external entity processing by default, and most XML documents don’t deal with external entities.


Of course, some Golang apps can still be vulnerable to XXE. You can easily find Go bindings for libxml2, which is a full-featured XML parser and has support for external entities. This is why XXEs in Golang are merely surprisingly hard, rather than impossible 🙂 But, by default, most developers will use the built-in encoding/xml library, which makes the entire ecosystem more secure.

Start your journey towards writing better software, and watch this space for new content.