Skip to content

Commit a8a30a5

Browse files
authored
Merge pull request #59 from clue-labs/target
Fix connecting to IPv6 address via SOCKS5 and validate target URI so hostname can not contain excessive URI components
2 parents f42f2db + d537361 commit a8a30a5

File tree

2 files changed

+88
-4
lines changed

2 files changed

+88
-4
lines changed

src/Server.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@
77
use React\Promise;
88
use React\Promise\Deferred;
99
use React\Promise\PromiseInterface;
10-
use React\Promise\CancellablePromiseInterface;
1110
use React\Socket\ConnectorInterface;
1211
use React\Socket\Connector;
1312
use React\Socket\ConnectionInterface;
1413
use React\EventLoop\LoopInterface;
1514
use \UnexpectedValueException;
1615
use \InvalidArgumentException;
17-
use \RuntimeException;
1816
use \Exception;
1917

2018
class Server extends EventEmitter
@@ -320,9 +318,21 @@ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamRead
320318

321319
public function connectTarget(ConnectionInterface $stream, array $target)
322320
{
321+
$uri = $target[0];
322+
if (strpos($uri, ':') !== false) {
323+
$uri = '[' . $uri . ']';
324+
}
325+
$uri = $uri . ':' . $target[1];
326+
327+
// validate URI so a string hostname can not pass excessive URI parts
328+
$parts = parse_url('tcp://' . $uri);
329+
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
330+
return Promise\reject(new InvalidArgumentException('Invalid target URI given'));
331+
}
332+
323333
$stream->emit('target', $target);
324334
$that = $this;
325-
$connecting = $this->connector->connect($target[0] . ':' . $target[1]);
335+
$connecting = $this->connector->connect($uri);
326336

327337
$stream->on('close', function () use ($connecting) {
328338
$connecting->cancel();

tests/ServerTest.php

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public function testHandleSocksConnectionWillEndOnInvalidData()
152152
$connection->emit('data', array('asdasdasdasdasd'));
153153
}
154154

155-
public function testHandleSocksConnectionWillEstablishOutgoingConnection()
155+
public function testHandleSocks4ConnectionWithIpv4WillEstablishOutgoingConnection()
156156
{
157157
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
158158

@@ -165,6 +165,80 @@ public function testHandleSocksConnectionWillEstablishOutgoingConnection()
165165
$connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
166166
}
167167

168+
public function testHandleSocks4aConnectionWithHostnameWillEstablishOutgoingConnection()
169+
{
170+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
171+
172+
$promise = new Promise(function () { });
173+
174+
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
175+
176+
$this->server->onConnection($connection);
177+
178+
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
179+
}
180+
181+
public function testHandleSocks4aConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection()
182+
{
183+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
184+
185+
$this->connector->expects($this->never())->method('connect');
186+
187+
$this->server->onConnection($connection);
188+
189+
$connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "tls://example.com:80?" . "\x00"));
190+
}
191+
192+
public function testHandleSocks5ConnectionWithIpv4WillEstablishOutgoingConnection()
193+
{
194+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
195+
196+
$promise = new Promise(function () { });
197+
198+
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
199+
200+
$this->server->onConnection($connection);
201+
202+
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
203+
}
204+
205+
public function testHandleSocks5ConnectionWithIpv6WillEstablishOutgoingConnection()
206+
{
207+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
208+
209+
$promise = new Promise(function () { });
210+
211+
$this->connector->expects($this->once())->method('connect')->with('[::1]:80')->willReturn($promise);
212+
213+
$this->server->onConnection($connection);
214+
215+
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x04" . inet_pton('::1') . "\x00\x50"));
216+
}
217+
218+
public function testHandleSocks5ConnectionWithHostnameWillEstablishOutgoingConnection()
219+
{
220+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
221+
222+
$promise = new Promise(function () { });
223+
224+
$this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
225+
226+
$this->server->onConnection($connection);
227+
228+
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
229+
}
230+
231+
public function testHandleSocks5ConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection()
232+
{
233+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
234+
235+
$this->connector->expects($this->never())->method('connect');
236+
237+
$this->server->onConnection($connection);
238+
239+
$connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x15" . "tls://example.com:80?" . "\x00\x50"));
240+
}
241+
168242
public function testHandleSocksConnectionWillCancelOutputConnectionIfIncomingCloses()
169243
{
170244
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();

0 commit comments

Comments
 (0)