CVE-2017-18016 (Rust, published 2018-01-11T16:29Z)
Description: value: Parity Browser 1.6.10 and earlier allows remote attackers to bypass the Same Origin Policy and obtain sensitive information by requesting other websites via the Parity web proxy engine (reusing the current website's token, which is not bound to an origin).
Severity: MEDIUM (CVSS v3 base score: 5.3)
Weakness: CWE-346 \u2014 Origin Validation Error
The product does not properly verify that the source of data or communication is valid.
Repository: https://github.com/paritytech/parity
Files changed:
dapps/src/lib.rs
dapps/src/tests/fetch.rs
dapps/src/tests/helpers/mod.rs
dapps/src/web.rs
js/src/api/rpc/signer/signer.js
js/src/jsonrpc/interfaces/signer.js
js/src/views/Web/store.js
js/src/views/Web/store.spec.js
js/src/views/Web/web.js
parity/dapps.rs
rpc/src/v1/helpers/signer.rs
rpc/src/v1/impls/signer.rs
rpc/src/v1/traits/signer.rs
Commit message: Domain-locked web tokens. (#5894)

* Domain-locking web tokens.

* JS part.

* Fix linting issues.


Vulnerable code:
use jsonrpc_http_server::{self as http, hyper};
	/// Should return true if token is a valid web proxy access token.
	fn is_web_proxy_token_valid(&self, token: &str) -> bool;
impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
	fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) }
	let (server, fetch) = serve_with_fetch("token");
	let (server, fetch) = serve_with_fetch("token");
	let (server, fetch) = serve_with_fetch("xolrg9fePeQyKLnL");
	let (server, fetch) = serve_with_fetch("token");
	let (server, fetch) = serve_with_fetch("test");
	let (server, fetch) = serve_with_fetch("token");
	let (server, fetch) = serve_with_fetch("token");
	let (server, fetch) = serve_with_fetch("token");
	let (server, fetch) = serve_with_fetch("token");
pub fn serve_with_fetch(web_token: &'static str) -> (Server, FakeFetch) {
			.web_proxy_tokens(Arc::new(move |token| &token == web_token))
			web_proxy_tokens: Arc::new(|_| false),
		match token {
			Some(token) if self.web_proxy_tokens.is_web_proxy_token_valid(token) => {},
		}
  generateWebProxyAccessToken () {
      .execute('signer_generateWebProxyAccessToken');
    params: [],
    transaction(() => {
      let url = (_url || this.nextUrl).trim().replace(/\/+$/, '');
      if (!hasProtocol.test(url)) {
        url = `https://${url}`;
      }
      this.setNextUrl(url);
      this.setCurrentUrl(this.nextUrl);
  generateToken = () => {
      .generateWebProxyAccessToken()
        store.gotoUrl();
        expect(store.currentUrl).to.equal('https://parity.io');
        store.gotoUrl('google.com');

        expect(store.currentUrl).to.equal('https://google.com');
    return this.store.generateToken();
		let web_proxy_tokens = Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token));
	web_proxy_tokens: Mutex<TransientHashMap<String, ()>>,
	pub fn is_valid_web_proxy_access_token(&self, token: &String) -> bool {
		self.web_proxy_tokens.lock().contains_key(&token)
	pub fn generate_web_proxy_access_token(&self) -> String {
		tokens.insert(token.clone(), ());
	fn generate_web_proxy_token(&self) -> Result<String, Error> {
		Ok(self.signer.generate_web_proxy_access_token())
		/// Generates new web proxy access token.
		fn generate_web_proxy_token(&self) -> Result<String, Error>;

Fixed code:
use jsonrpc_http_server::{self as http, hyper, Origin};
	/// Should return a domain allowed to be accessed by this token or `None` if the token is not valid
	fn domain(&self, token: &str) -> Option<Origin>;
impl<F> WebProxyTokens for F where F: Fn(String) -> Option<Origin> + Send + Sync {
	fn domain(&self, token: &str) -> Option<Origin> { self(token.to_owned()) }
	let (server, fetch) = serve_with_fetch("token", "https://parity.io");
	let (server, fetch) = serve_with_fetch("token", "https://parity.io");
	let (server, fetch) = serve_with_fetch("xolrg9fePeQyKLnL", "https://contribution.melonport.com");
	let (server, fetch) = serve_with_fetch("token", "https://parity.io");
#[test]
fn should_return_error_on_non_whitelisted_domain() {
	// given
	let (server, fetch) = serve_with_fetch("token", "https://ethcore.io");

	// when
	let response = request(server,
		"\
			GET / HTTP/1.1\r\n\
			Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
			Connection: close\r\n\
			\r\n\
		"
	);

	// then
	response.assert_status("HTTP/1.1 400 Bad Request");
	assert_security_headers_for_embed(&response.headers);

	fetch.assert_no_more_requests();
}

	let (server, fetch) = serve_with_fetch("test", "https://parity.io");
	let (server, fetch) = serve_with_fetch("token", "ftp://parity.io");
	let (server, fetch) = serve_with_fetch("token", "https://parity.io");
	let (server, fetch) = serve_with_fetch("token", "https://parity.io");
	let (server, fetch) = serve_with_fetch("token", "https://parity.io");
pub fn serve_with_fetch(web_token: &'static str, domain: &'static str) -> (Server, FakeFetch) {
			.web_proxy_tokens(Arc::new(move |token| {
				if &token == web_token { Some(domain.into()) } else { None }
			}))
			web_proxy_tokens: Arc::new(|_| None),
		let domain = match token.and_then(|token| self.web_proxy_tokens.domain(token)) {
			Some(domain) => domain,
		};
		if !target_url.starts_with(&*domain) {
			return Err(State::Error(ContentHandler::error(
				StatusCode::BadRequest, "Invalid Domain", "Dapp attempted to access invalid domain.", Some(&target_url), self.embeddable_on.clone(),
			)));
		}

  generateWebProxyAccessToken (domain) {
      .execute('signer_generateWebProxyAccessToken', domain);
    params: [{
      type: String,
      desc: 'Domain for which the token is valid. Only requests to this domain will be allowed.',
      example: 'https://parity.io'
    }],
    let url = (_url || this.nextUrl).trim().replace(/\/+$/, '');
    if (!hasProtocol.test(url)) {
      url = `https://${url}`;
    }
    return this.generateToken(url).then(() => {
      transaction(() => {
        this.setNextUrl(url);
        this.setCurrentUrl(this.nextUrl);
      });
  generateToken = (_url) => {
      .generateWebProxyAccessToken(_url)
        return store.gotoUrl().then(() => {
          expect(store.currentUrl).to.equal('https://parity.io');
        });
        return store.gotoUrl('google.com').then(() => {
          expect(store.currentUrl).to.equal('https://google.com');
        });
		let web_proxy_tokens = Arc::new(move |token| signer.web_proxy_access_token_domain(&token));
use http::Origin;
	web_proxy_tokens: Mutex<TransientHashMap<String, Origin>>,
	pub fn web_proxy_access_token_domain(&self, token: &String) -> Option<Origin> {
		self.web_proxy_tokens.lock().get(token).cloned()
	pub fn generate_web_proxy_access_token(&self, domain: Origin) -> String {
		tokens.insert(token.clone(), domain);
	fn generate_web_proxy_token(&self, domain: String) -> Result<String, Error> {
		Ok(self.signer.generate_web_proxy_access_token(domain.into()))
		/// Generates new web proxy access token for particular domain.
		fn generate_web_proxy_token(&self, String) -> Result<String, Error>;