Git Product home page Git Product logo

blinksocks's Introduction

blinksocks

version downloads license dependencies devDependencies

Travis Coverage %e2%9d%a4

A framework for building composable proxy protocol stack.

Looking for GUI? Here it is: https://github.com/blinksocks/blinksocks-gui

Features

Getting Started

Requirements

Install or Upgrade

You can get the latest blinksocks via package manager yarn or npm.

NOTE: Node.js comes with npm installed so you don't have to install npm individually.

$ npm install -g blinksocks

Run blinksocks

$ blinksocks --help

For configuring blinksocks, please refer to Configuration.

Documents

For Users

  1. Usage
  2. Configuration
  3. Presets
  4. Examples

For Developers

  1. Preparation
  2. Architecture
  3. API
  4. Benchmark

Contributors

See contributors.

License

Apache License 2.0

blinksocks's People

Contributors

5aaee9 avatar greenkeeper[bot] avatar greenkeeperio-bot avatar micooz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blinksocks's Issues

Allow to custom DNS servers

blinksocks server hostname is resolved by dns servers(system network settings) locally before client make connections.

Sometimes we will encounter many ENOTFOUND problems once dns.lookup fail to resolve.

To deal with this problem, I introduce an option dns to set dns servers to dns module.

"dns": [ip1, ip2, ...]

Error: Invalid arguments: hostname must be a string or falsey

This may happened when host is invalid:

  async connect({host, port}, callback) {
    logger.info(`[socket] [${this._id}] connecting to: ${host}:${port}`);
    this._tracks.push(`${host}:${port}`);
    try {
      const ip = await dnsCache.get(host);
      this._fsocket = net.connect({host: ip, port}, callback);
      this._fsocket.on('error', this.onError);
      this._fsocket.on('close', this.onClose);
      this._fsocket.on('data', this.onBackward);
    } catch (err) {
      - logger.error(err.message);
      + logger.error(`[socket] [${this._id}] connect to ${host}:${port} failed: ${err.message}`); // log details to see what host is
    }
  }

An in-range update of husky is breaking the build 🚨

Version 0.13.3 of husky just got published.

Branch Build failing 🚨
Dependency husky
Current Version 0.13.2
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As husky is “only” a devDependency of this project it might not break production or downstream projects, but “only” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this 💪


Status Details
  • continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details
Commits

The new version differs by 9 commits .

See the full diff.

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Disable a server by prefixing a '-'

You can prefixing a '-' on a server to disable it temporary:

{
  "host": "localhost",
  "port": 5555,
  "servers": [
    "test.com:3000",
    "-test.com:3001" // disable this server temporary
  ],
  "key": "oh my secret key",
  "frame": "origin",
  "frame_params": "",
  "crypto": "",
  "crypto_params": "",
  "protocol": "ss-aead",
  "protocol_params": "aes-256-gcm,ss-subkey",
  "obfs": "",
  "obfs_params": "",
  "log_level": "silly"
}

connection terminated while downloading a large file

On server side, fsocket handles the download process, download speed is fast while bsocket upload speed is slow.

<--bsocket(slow)--> S <--fsocket(fast)--> a large amount of data(remote server)

Once download was done, the remote server close the connection between fsocket and itself, blinksocks server here close bsocket at the same time, this causes the download to terminate at once:

this._fsocket.on('close', this.onClose);
onClose() {
  const sockets = [this._bsocket, this._fsocket];
  for (const socket of sockets) {
    if (socket !== null && !socket.destroyed) {
      socket.destroy();
      this._onClose(this); // notify hub to remove this one
      this.dumpTrack();
    }
  }
  this._bsocket = null;
  this._fsocket = null;
  clearInterval(this._timeout_timer);
}

Add manager api

Like shadowsocks:

  • An interface(socket) is listening at /var/run/shadowsocks-manager.sock.
  • Managers can access the interface via UDP.
  • Dynamic configure is possible.

Write logs to home directory

There is a permission problem when launch blinksocks-desktop via blinksocks-desktop.app. By default, blinksocks writes logs to the execution path which may not has write permission. Write in the home directory is a system-wide approach to avoid crashing due to EACCES: permission denied.

Robust design for middlewares

This would be nice if we can split presets into several projects, just like babel-preset-xxx do.

In config.json:

{
  "presets": [
    {
      "name": "ss-base",
      "params": {}
    },
    {
      "name": "ss-aead-cipher",
      "params": {
        "method": "aes-128-gcm",
        "info": "ss-subkey"
      }
    }
  ]
}

You can combine many kinds of presets in a particular order to form your own Protocol, not just "predefined" four types: frame, crypto, protocol and obfs.

Robust design for middleware system can be more flexible and volatile. And the aim of blinksocks is not only to bypass the damn firewall, but also to design any type of proxy protocols in an comfortable way.

一种利用分组密码的工作模式基于shadowsocks设计的新型反探测代理协议

一种利用分组密码的工作模式基于shadowsocks设计的新型反探测代理协议

0x00 背景

早在2015年8月底,breakwa11就在《ShadowSocks协议的弱点分析和改进》一文中就分析了shadowsocks(下称ss)的弱点和可能的攻击手段。2017年1月,新型认证加密协议AEAD Ciphers诞生,以弥补Stream Ciphers无法认证数据的不足,并且保证全量数据的完整性,相关讨论可以参考SIP004

本文探讨的协议是对shadowsocks的Stream Ciphers的补充和增强。虽然社区已经不再建议继续使用Stream Ciphers,但我认为可以另辟蹊径再续一秒。

0x01 核心**

不同人有不同的见解,展开前,先提出个人的愚见,望理性吐槽。

数据的完整性不是刚需,隐匿性最重要。

ss协议具有高度隐匿性,越是复杂的设计越容易暴露更多问题,从而破坏隐匿性。AEAD引入认证过程,将协议复杂度提升了一个层次,完美解决了数据篡改的问题,确保了完整性。但我认为无需面面俱到,画蛇添足,只需要确保协议在正常数据流上增加信息(iv以及addressing部分)的可靠性,至于负载数据部分则完全可以交由应用处理。比如举一个例子:使用ss承载https流量,机密性、完整性、抗重放、抗各类攻击已经得到足够保证,无需再在ss上做一层校验。简言之,ss在两端应用间应当尽可能透明。

0x02 问题分析

攻击者可以通过篡改数据的方式来探测服务端是否运行着ss服务,具体实施方法和详细分析可以参考《为何 shadowsocks 要弃用一次性验证 (OTA)》这篇文章,这里不再过多阐述。下面以分组密码的工作模式为切入点,来分析各分组密码的工作模式对协议设计的影响

AES处理的数据分组长度为128bit,也就是每16字节为一块(block)。ss原协议的atyp位于第一块的第一个字节;addr(这里只讨论atyp为hostname的变长情况)开始部分一定位于第一块,若len(addr) + 1 <= 15,则addr全部位于第一块。

AES中分组密码的工作模式有多种,常用的是CBC、CFB、OFB、CTR和GCM。Stream Cipher建议的只有两种,一种是CFB,另一种是CTR。那么CFB和CTR在解密经过篡改后的数据时的表现如何呢?

CFB

cfb-ciphertext

cfb-iv

  • 篡改第一块密文分组的任意字节,导致第一块明文分组的对应字节发生改变,同时破坏了下一个明文分组。
  • 篡改IV的任意字节,导致第一块明文分组错误。

CTR

ctr-ciphertext

ctr-iv

  • 篡改第一块密文分组的任意字节,导致第一块明文分组的对应字节发生改变。
  • 篡改IV的任意字节,导致所有明文分组错误。

那么其他没有被建议的工作模式又如何表现?

CBC

cbc-ciphertext

cbc-iv

  • 篡改第一块密文分组的任意字节,导致第一块明文分组错误,并且导致第二块明文分组对应字节发生改变。
  • 篡改IV的任意字节,导致第一块明文分组对应字节发生改变。

OFB

ofb-ciphertext

ofb-iv

  • 篡改第一块密文分组的任意字节,导致第一块明文分组对应位置发生改变。
  • 篡改IV的任意字节,导致所有明文分组错误。

希望发生的情况:

  • 正着说:攻击者在atyp或者IV上实施的任意篡改,会直接导致addr的改变。
  • 反过来:服务端通过检查addr的合法性,可以知道atyp或IV遭到了篡改。

通过上述分析,按照ss原先的设计,无论Stream Cipher使用何种模式都无法满足这个要求(不能确保addr能响应IV或者atyp的改变)。要满足这个要求,必须移动addr的位置,使得addr和数据篡改产生关联,这时候就要好好利用分组密码的工作模式了。

0x03 协议设计

新的设计十分简单,在ss原协议的基础上,将ATYP换成ALEN(表示ADDR的长度,一定不会超过255个字节),随后增加一个15字节的PADDING,并总是使用CFB工作模式:

// Client => Server, TCP Stream
+------+------+-----------+----------+----------+----------+---------+
|  IV  | ALEN |  PADDING  | DST.ADDR | DST.PORT |   DATA   |   ...   |
+------+------+-----------+----------+----------+----------+---------+
|  16  |  1   |    15     | Variable |    2     | Variable |   ...   |
+------+------+-----------+----------+----------+----------+---------+
       |<----------------- AES-XXX-CFB Encrypted ------------------->|

服务端收到足够的数据后,通过IV和共享密钥开始解密,随后进行如下检查:

  1. 检查填充字节PADDING是否为预期值。
  2. 通ALEN取得addr并检查addr的合法性(可通过正则表达式),如:isIPv4(addr)、isIPv6(addr)、isHostName(addr)。

协议分析:

  • 若攻击者篡改IV,那ALEN和填充字节必然会被破坏。
  • 若攻击者篡改ALEN,那addr必然会被破坏。
  • 攻击者只能既篡改IV又篡改ATYP,但是很难构造一个适当的组合来保证第二个明文分组(addr)是合法的。

检查出问题后,如何处置就是socket层的任务了,不在本文的讨论范围内。

几个问题:

1. 把ATYP换成ALEN的目的是什么?

ss原始协议是通过ATYP来判断addr的类型,依赖一个不可靠字节的值来做判断是存在很大隐患的。实际上完全可以直接检查addr的结构来判断地址类型。对于变长hostname的情形,需要一个字节来判断addr的长度,ss原先的设计是参考Socks5协议,把addr[0]当做hostname的长度,这个地方又存在和ATYP相同隐患。因此把addr[0]提到第一个分组的第一个字节,使得对ALEN的篡改可以直接在addr上体现出来。

2. 设计PADDING的意义是什么?

将addr挤到第二个密文分组内,使得篡改ALEN后addr会错乱。

3. 检查PADDING的意义是什么?

使得篡改IV后,PADDING会错乱。

4. 为什么只能选择CFB工作模式?

其实只要出现发生一对一篡改变化的工作模式都不能利用。而CFB不存在一对一篡改变化的情况,都是一对多:一个字节变了,会影响至少16个字节。

5. ADDR是IP的情况?

如果addr为IP地址,其实可以转为字符串(ascii码)后再发送,这样更有利于暴露错误和检查,而不要使用4字节(IPv4)或16字节(IPv6)数值的表示方式,因为这样随意篡改都会得到一个合法的IP地址,从而无法作出判断。

6. 篡改其他地方的可行性?

如果攻击者另辟蹊径:

  • 篡改PADDING,必然通不过检查。
  • 篡改ADDR,有较低的概率得到另一个合法的地址。
  • 无法精准篡改PORT,因为ADDR是变长的,导致PORT无法定位。

对于第二种篡改,会破坏第三个密文分组,服务端发现目标地址不存在、无法连接或者连接成功后发送的DATA被拒绝,便无法向攻击者给出正确响应。攻击者采取这种方法得手不是完全可靠的。

0x04 重放攻击

对CFB工作模式的有效攻击是重放攻击(Replay Attack)。攻击者可以事先收集一些以往合法的密文分组,但不能改变IV,改变IV的重放会导致后续解密全部错误,因为密文分组是和开始的IV强相关联的。

cfb-encryption

攻击者希望通过替换部分密文分组,来确保第二个密文分组总是合法的,从而逃避服务端对addr的检查。同时替换第一、第二个密文分组才能达到这个效果。但是很遗憾,重放第一个密文分组会破坏第一个明文分组,导致PADDING检查错误。因此重放攻击对此协议无效。

cfb-replay-attack

0x05 等待超时攻击

由于协议只能在接收到足够的数据后才能做进一步检查,如果攻击者故意制造不够解密长度的数据,服务端会陷入等待然后超时。攻击者一点一点增加数据量,评估服务端是否响应(哪怕是出错断开连接),就可以确定是否满足协议设计条件从而做出判断。

应对这种攻击,可以规定首次接受的数据长度必须超过足够服务端完成所有检查的数据长度即可。

0x06 缺点和局限

  • 只依赖工作模式的特性来确保addr的完整性没有HMAC科学(能保证到什么程度还需进一步量化)。
  • 只能使用CFB模式。
  • 不能向后兼容。

0x07 协议实现(Show Me The Code)

#70

测试参数配置:

  presets: [{
    "name": "exp-base-with-padding",
    "params": {
      "salt": "any string"
    }
  }, {
    "name": "ss-stream-cipher",
    "params": {
      "method": "aes-256-cfb"
    }
  }],

0x08 实验代码

const crypto = require('crypto');

const method = 'aes-256-cfb';
// const method = 'aes-256-cbc';
// const method = 'aes-256-ofb';
// const method = 'aes-256-ctr';
const key = Buffer.alloc(32);

let iv = Buffer.alloc(16);

function decrypt(cipherText) {
  const decipher = crypto.createDecipheriv(method, key, iv);
  return Buffer.concat([decipher.update(cipherText)]);
}

const cipherText = Buffer.from(
  '00000000000000000000000000000000' +
  '00000000000000000000000000000000' +
  '00',
  'hex'
);

const tamperedCipherText = Buffer.from(
  'ff000000000000000000000000000000' +
  '00000000000000000000000000000000' +
  '00',
  'hex'
);

console.log(method);
console.log('<== before tamper ==>');

let plainText = decrypt(cipherText);
console.log('len =', plainText.length, '\nbuf =', plainText);

// iv[0] = 0xff;

console.log('<== after tamper ==>');

plainText = decrypt(tamperedCipherText);
console.log('len =', plainText.length, '\nbuf =', plainText);

Share the same config.json between clients and servers

Current version we must specify two different config.json to run blinksocks:

$ blinksocks run -c client.json
$ blinksocks run -c server.json

It's a little bit complex, maybe we can maintain only one config.json in the next version:

$ blinksocks client -c config.json
$ blinksocks server -c config.json

How about blinksocks over TLS

By making use of tls.Server and tls.TLSSocket, we can switch original socket to TLSSocket to provide TLS facilities used widely on the Internet.

The purpose of doing this is try to protect server from detection. It's a kind of obfuscation.

Destination port is wrong when relay ipv6 address

This is a mistake:

case ATYP_V6:
  if (buffer.length < 19) {
    fail(`invalid length: ${buffer.length}`);
    return;
  }
  addr = ip.toString(buffer.slice(1, 17));
  port = buffer.slice(16, 18).readUInt16BE(0); // should be buffer.slice(17, 19)
  offset += 16;
  break;

Allow to provide a list of sni to obfs-tls1.2-ticket

We require only one sni to pass to obfs-tls1.2-ticket, why not support a list of sni?

{
  "name": "obfs-tls1.2-ticket",
  "params": {
    "sni": "www.bing.com"
  }
}

A compatibility mode:

{
  "name": "obfs-tls1.2-ticket",
  "params": {
    "sni": ["www.bing.com", "another.one"]
  }
}

Support configuration file with ".js" format

Consider that comments are not allowed in .json, .js should be a better choice to store configurations with detailed comments:

// config.js
module.exports = {
  // comment anywhere
  "host": "localhost",

  "port": 5555,
  ...
};

An in-range update of babel-preset-env is breaking the build 🚨

Version 1.5.2 of babel-preset-env just got published.

Branch Build failing 🚨
Dependency babel-preset-env
Current Version 1.5.1
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As babel-preset-env is “only” a devDependency of this project it might not break production or downstream projects, but “only” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this 💪

Status Details
  • continuous-integration/travis-ci/push The Travis CI build failed Details

Release Notes v1.5.2

v1.5.2 (2017-06-07)

🐛 Bug Fix

browser targets should be overridden by explicit targets, and we inadvertently broke this when we landed string version support.

Commits

The new version differs by 5 commits.

  • f4ce53c 1.5.2
  • 00490ac Merge pull request #346 from babel/issue345
  • 2896925 Add node 8 to travis (#347)
  • 0017129 Ensure explicit targets always override browsers key targets
  • e23bd47 README: Add string type as valid node target value (#337) [skip ci]

See the full diff

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Print connection track line once a socket was closed

A track line contains several parts:

remote endpoint, target endpoint, d xxx xxx u xxx xxx ...

d means download, u means upload.

This is very helpful to analyse the history of a connection.

For example:

info: [socket] [1] summary: 127.0.0.1:35632 www.bing.com:443 d 368 u 268 2976 d 2880 u 1424 d 1337 u 113 d 208 u 101  ...  4416 d 4320 u 2976 d 2880 u 1168 d 1077 u 3088 d 3002 u 85 d 176

Enhancement: error handling

When an error occurred(sometimes in preset), we just log them to the console/file, no further things can be done until socket on the other side timeout.

The unexpected incoming data will break connection every now and then, we don't have a proper mechanism to handle this kind of error.

An in-range update of babel-preset-env is breaking the build 🚨

Version 1.3.0 of babel-preset-env just got published.

Branch Build failing 🚨
Dependency babel-preset-env
Current Version 1.2.2
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As babel-preset-env is “only” a devDependency of this project it might not break production or downstream projects, but “only” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this 💪


Status Details
  • continuous-integration/travis-ci/push The Travis CI build failed Details
Release Notes v1.3.0

v1.3.0 (2017-03-30)

🐛 Bug Fix

We now properly check for Symbol.species support in ArrayBuffer and include the
polyfill if necessary. This should, as a side effect, fix ArrayBuffer-related
errors on IE9.

💅 Polish

We've simplified things by adding electron as a target instead of doing a bunch of
things at runtime. Electron targets should now also be displayed in the debug output.

If you are targeting the node environment exclusively, the always-included web polyfills
(like dom.iterable, and a few others) will now no longer be included.

📝 Documentation

🏠 Internal

  • npmignore: Add related to build data and codecov. (#216) (@yavorsky)
Commits

The new version differs by 8 commits .

  • 8b2dc4f 1.3.0
  • 6ebf857 Update changelog
  • 046f326 Add check for ArrayBuffer[Symbol.species] (#233)
  • aead61c Fill data with electron as a target. (#229)
  • 48a329b separate default builtins for platforms (#226)
  • a4d585c remove deprecated projects (#223) [skip ci]
  • 88cbe17 Merge pull request #216 from babel/update-npmignore
  • cf94af3 npmignore: Add related to build data and codecov.

See the full diff.

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

HTTP proxy doesn't work on Windows

blinksocks can handle socks5/socks4/socks4a/http requests at the same time, http requests works well on my macOS. But unfortunately, http proxy requests of Windows are different from macOS or curl.

There is a bug in HttpRequestMessage.js#L4:

// +----------------------------------------+
// |  CONNECT www.bing.com:443 HTTP/1.1\r\n |
// |  Host: www.bing.com:443\r\n            |
// |  [...Headers]                          |
// |  \r\n                                  |
// +----------------------------------------+
if (methods.includes(method)) {
  const host = lines[1].split(' ')[1]; // here is a bug
  return new HttpRequestMessage({
    METHOD: Buffer.from(method),
    URI: Buffer.from(uri),
    VERSION: Buffer.from(version),
    HOST: Buffer.from(host)
  });
}

It treat the second line of HTTP message as Host which likely appear in the different line of headers.

TypeError: Cannot read property 'bufferSize' of null

at Socket.onForward (/usr/lib/node_modules/blinksocks/lib/core/socket.js:1:4202)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
at Socket.Readable.push (_stream_readable.js:134:10)
at TCP.onread (net.js:547:20)

Proposal: multi-server mode

This feature/function allow blinksocks to auto-detect/switch to the fastest server in the servers list.

Possible configuration here:

{
  "host": "localhost",
  "port": 1080,
  "servers": [
    "node1.foo.com:6666",
    "node2.bar.com:6667",
    "proxy.baz.com:8080"
  ],
  ...
}

Related Issues: #22

Multiple servers use different configurations

blinksocks v2.3.0 support multi-server mode:

{
  ...
  "servers": [
    "localhost:7777",
    "localhost:7778"
  ],
  ...
}

All servers share a same configuration(key and presets), this is non-flexible.

A better design for multi-server mode allows user specify a list of servers with different configuration.

{
  ...
  "servers": [
    {
      "host": "localhost",
      "port": 7777,
      "key": "secret1",
      "presets": [...]
    },
    {
      "host": "localhost",
      "port": 7778,
      "key": "secret2",
      "presets": [...]
    },
    ...
  ],
  ...
}

Lack of timeout mechanism

Inactive connection won't be closed by blinksocks, this may cause waste of resources.

We can set a count-down timer(timeout) for each connection to improve resource utilization.

The timeout can be set by user via config.json or in command line:

// config.json
{
  ...
  timeout: 600, // seconds
  ...
}

Consider redirecting TCP stream to other host/port rather than close connections

Involved code

if (action.type === PROCESSING_FAILED) {
  const message = action.payload;
  const timeout = Utils.getRandomInt(10, 40);
  logger.error(`connection will be closed in ${timeout}s due to: ${message}`);
  Profile.fatals += 1;
  setTimeout(() => this.onClose(), timeout * 1e3);
}

Current behaviour

The connection will be closed after random seconds when fail() is called in a preset.

Enhanced behavior

The following data will be redirected to hostname:port when fail() is called in a preset.

Also see

https://github.com/shadowsocksr/shadowsocksr/blob/dev/shadowsocks/tcprelay.py#L466-L476

Hot reload config.json

Just make it convenient in both development and production:

$ blinksocks client -c config.json --watch

Verify DST.ADDR of "presets/ss-base"

+------+----------+----------+----------+
| ATYP | DST.ADDR | DST.PORT |   DATA   |
+------+----------+----------+----------+
|  1   | Variable |    2     | Variable |
+------+----------+----------+----------+

When use stream ciphers, original implementation lack of verification on DST.ADDR.

Only check ATYP(is one of [0x01, 0x03, 0x04]) can be lose integrity and become easy to sniff.

We can simply check if DST.ADDR is valid or not to avoid attacking to ATYP:

/**
 * verify hostname
 *
 * @param hostname
 * @returns {boolean}
 *
 * @reference
 *   http://stackoverflow.com/questions/1755144/how-to-validate-domain-name-in-php
 */
function isValidHostname(hostname) {
  // overall length check
  if (hostname.length < 1 || hostname.length > 253) {
    return false;
  }
  // valid chars check
  if (/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i.test(hostname) === false) {
    return false;
  }
  // length of each label
  if (/^[^.]{1,63}(\.[^.]{1,63})*$/.test(hostname) === false) {
    return false;
  }
  return true;
}

Proposal: implement new protocol preset aead v2

aead.js has ability to ensure authenticity on data, but it cannot resist replay attacks.

This proposal aims to implement a protocol preset aead2 which can prevent both server and client from replay attacks.

Possible design is:

 * @protocol
 *
 *   # TCP handshake & chunk
 *   +-----------+-----------+-------+----------+-------------------+----------+
 *   |  PADDING  |    SEQ    |  LEN  |  HMAC-A  |      PAYLOAD      |  HMAC-B  |
 *   +-----------+-----------+-------+----------+-------------------+----------+
 *   |     6     |     6     |   4   |  Fixed   |      Variable     |  Fixed   |
 *   +-----------+-----------+-------+----------+-------------------+----------+
 *   |<---------- header ----------->|
 *
 * @explain
 *   1. LEN is the total length of the packet.
 *   2. PADDING is random generated.
 *   3. HMAC-A = mac(encrypt(header)).
 *   4. HMAC-B = mac(encrypt(PAYLOAD)).
 *   5. The length of HMAC depends on message digest algorithm.
 *   6. Encrypt-then-MAC (EtM) is performed for calculating HMAC.
 *   7. SEQ is set to 0 when client make handshake to server.
 *   8. SEQ is random generate by server, then sent to client.
 *   9. SEQ should +1 once a packet is out, on both client and server.
 *   10. SEQ is verified on both client and server.

Add random padding based on AEAD ciphers

Shadosocks AEAD Protocol

# TCP stream
+---------+------------+------------+-----------+
|  SALT   |   chunk_0  |   chunk_1  |    ...    |
+---------+------------+------------+-----------+
|  Fixed  |  Variable  |  Variable  |    ...    |
+---------+------------+------------+-----------+

# TCP chunk
+---------+-------------+----------------+--------------+
| DataLen | DataLen_TAG |      Data      |   Data_TAG   |
+---------+-------------+----------------+--------------+
|    2    |    Fixed    |    Variable    |    Fixed     |
+---------+-------------+----------------+--------------+

The problem is the protocol split data into chunks, where the length of each chunk is probably
the same
= (2 + 16 + 0x3FFF - 1 + 16):

AEAD reserves the first two bits of DataLen, limits the max chunk size to 0x3FFF to prevent DataLen from overflowing(or other consideration I haven't found). Payload length greater than 0x3FFF will be split into chunks, then the fixed length chunk appears. This can be treated as a distinguishing feature when a large amount of data sent from client.

The implementation of shadowsocks python:

while plen > 0:
    mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \
        else AEAD_CHUNK_SIZE_MASK
    c = self.encrypt_chunk(data[:mlen])
    ctext.append(c)
    data = data[mlen:]
    plen -= mlen

Possible solutions

  1. Random Split
    Payload length greater than 0xFFFF should be split into chunks with random length, rather than 0x3FFF.

  2. Random Padding
    By making use of nonce, we can simply insert a random padding into each chunk.

# TCP chunk
+----------------+---------+-------------+----------------+--------------+
| Random Padding | DataLen | DataLen_TAG |      Data      |   Data_TAG   |
+----------------+---------+-------------+----------------+--------------+
|    Variable    |    2    |    Fixed    |    Variable    |    Fixed     |
+----------------+---------+-------------+----------------+--------------+

The length of each chunk can be calculated from nonce:

padding length = encrypt(nonce).tag[0] * factor

The encrypt is a little different from aead encryption, it uses derived key to encrypt session nonce.

const cipher = crypto.createCipheriv(algorithm, key, nonce);
cipher.update(nonce);
cipher.final();
const tag = cipher.getAuthTag();

factor is used for expanding the range of padding because encrypt(nonce).tag[0] is 0~255.

When receiving, both sides calculate random padding length, drop random padding before verify DataLen. Subsequent steps are the same as AEAD.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.