Skip to content

Commit be2343b

Browse files
authored
Merge pull request #31 from 39ff/claude/security-review-33NY5
Add input validation and improve SOCKS5 authentication security
2 parents 02e7f47 + 56afd4c commit be2343b

2 files changed

Lines changed: 51 additions & 15 deletions

File tree

setup/generate.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,52 @@
1919
// the connection is direct to the target (not an HTTP proxy).
2020
$squid_socks = 'cache_peer %s parent %d 0 no-digest no-netdb-exchange connect-fail-limit=2 connect-timeout=8 round-robin no-query allow-miss proxy-only originserver name=%s %s';
2121

22+
// Reject any byte that could break squid.conf tokenization or inject
23+
// a new directive (whitespace, control chars, quotes, backslash, '#').
24+
// Applied to host/user/pass to prevent config injection from a tainted
25+
// proxyList.txt (e.g. one fetched from a remote URL).
26+
$reject_unsafe = '/[\s"#\\\\\x00-\x1F\x7F]/';
27+
2228
while ($line = fgets($proxies)){
2329
$line = trim($line);
30+
if ($line === '' || $line[0] === '#') {
31+
continue;
32+
}
2433
$proxyInfo = array_combine($keys, array_pad((explode(":", $line, 5)), 5, ''));
2534
$squid_conf = [];
2635
$cred = '';
2736
if(!$proxyInfo['host'] && !$proxyInfo['port']){
2837
continue;
2938
}
3039

40+
// Validate host: hostname or IPv4 literal. No shell/conf metachars.
41+
// IPv6 literals are not supported: the naive explode(":") parser above
42+
// cannot split "[::1]:8080:..." correctly, so ':' / '[' / ']' are rejected
43+
// to avoid giving the impression that IPv6 is accepted.
44+
if (preg_match($reject_unsafe, $proxyInfo['host']) ||
45+
!preg_match('/^[A-Za-z0-9._-]+$/', $proxyInfo['host'])) {
46+
fwrite(STDERR, "Skipping proxy with invalid host: " . rawurlencode($proxyInfo['host']) . PHP_EOL);
47+
continue;
48+
}
49+
// Validate port: 1-65535.
50+
if (!ctype_digit((string)$proxyInfo['port']) ||
51+
(int)$proxyInfo['port'] < 1 || (int)$proxyInfo['port'] > 65535) {
52+
fwrite(STDERR, "Skipping proxy with invalid port: " . rawurlencode((string)$proxyInfo['port']) . PHP_EOL);
53+
continue;
54+
}
55+
// Validate credentials: no whitespace/control chars/quotes/backslash/#.
56+
// (SOCKS5 RFC1929 allows up to 255 bytes of arbitrary octets, but we
57+
// conservatively reject bytes that would break squid.conf.)
58+
if (($proxyInfo['user'] !== '' && preg_match($reject_unsafe, $proxyInfo['user'])) ||
59+
($proxyInfo['pass'] !== '' && preg_match($reject_unsafe, $proxyInfo['pass']))) {
60+
fwrite(STDERR, "Skipping proxy with unsafe characters in credentials: " . $proxyInfo['host'] . PHP_EOL);
61+
continue;
62+
}
63+
if (strlen($proxyInfo['user']) > 255 || strlen($proxyInfo['pass']) > 255) {
64+
fwrite(STDERR, "Skipping proxy with credentials exceeding 255 bytes: " . $proxyInfo['host'] . PHP_EOL);
65+
continue;
66+
}
67+
3168
if(!$proxyInfo['scheme']){
3269
//open proxy server IP:Port Pattern.
3370
//No create gost

squid_patch/src/SocksPeerConnector.h

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,20 +127,15 @@ static inline bool socks5Connect(int fd,
127127
const bool hasAuth = (!user.empty() && !pass.empty());
128128

129129
/* --- greeting ---------------------------------------------------- */
130-
uint8_t greeting[4];
131-
size_t gLen;
132-
if (hasAuth) {
133-
greeting[0] = 0x05; /* VER */
134-
greeting[1] = 0x02; /* NMETHODS */
135-
greeting[2] = 0x00; /* NO AUTHENTICATION */
136-
greeting[3] = 0x02; /* USERNAME / PASSWORD */
137-
gLen = 4;
138-
} else {
139-
greeting[0] = 0x05;
140-
greeting[1] = 0x01;
141-
greeting[2] = 0x00;
142-
gLen = 3;
143-
}
130+
/* When credentials are configured, advertise ONLY USER/PASS to
131+
* prevent a rogue SOCKS5 server from silently downgrading to
132+
* "no authentication" and accepting traffic without verifying
133+
* the credentials the operator explicitly provided. */
134+
uint8_t greeting[3];
135+
greeting[0] = 0x05; /* VER */
136+
greeting[1] = 0x01; /* NMETHODS */
137+
greeting[2] = hasAuth ? 0x02 : 0x00; /* USER/PASS or NO AUTH */
138+
const size_t gLen = 3;
144139

145140
if (!syncSend(fd, greeting, gLen))
146141
return false;
@@ -182,7 +177,11 @@ static inline bool socks5Connect(int fd,
182177
return false; /* auth failed or wrong sub-negotiation version */
183178

184179
} else if (gResp[1] == 0x00) {
185-
/* no auth required */
180+
/* Server chose "no authentication". Only accept this when the
181+
* operator did NOT configure credentials; otherwise the server
182+
* is downgrading away from the authentication we required. */
183+
if (hasAuth)
184+
return false;
186185
} else {
187186
return false; /* unsupported or unacceptable method (includes 0xFF) */
188187
}

0 commit comments

Comments
 (0)